Hatena::ブログ(Diary)

Unix的なアレ

2008-02-23

初めてシェルスクリプトを書くときに知っておきたいこと

他の言語をを書き慣れてから、シェルスクリプトを書こうとするとしょうもない部分でハマったりすることがあります。「簡単な処理だからシェルスクリプトで書くか」と思っても無駄に時間がかかってしまっては仕方ないですよね。

今回は初めてシェルスクリプトを書くときに、ハマりそうな点について説明をしたいと思います。

実行権限をつける

単純にファイルを生成しても下記の用に実行しようとしても実行はできません。

# touch test

# ./test

sh: ./test: Permission denied

# touch foo

# ./foo

sh: ./foo: Permission denied

これはファイルに実行権限がついていないため、起きる現象です。

chmodコマンドを使って実行権限をつけてやりましょう。

# chmod +x test

# ./test

# chmod +x foo

# ./foo

エラーメッセージは返らずに、無事実行できたと思います。

まずシェルスクリプト用のファイルを生成したら実行権限をつけるようにしましょう。

実行するプログラムを指定する

よくおまじないと言われる記述部分です。これは、ここにおいてあるプログラムを使用してそれ以降を実行させましょうという意味になります。

今回はBourneShellを想定していますので下記のように指定をしましょう。

#!/bin/sh

Perlを書いたことある人なら似たような記述をしたことがあると思います。

#!/usr/bin/perl

#!/usr/local/bin/perl

#!/usr/bin/env perl

これは指定したPATHのPerlを使用しなさいって意味なので、shに関しても同様になります。

変数をセットする

自分はこれが最初はハマりました・・・かなり基本的な部分ですが他の言語とことなるので意識をしておく必要があります。

まず簡単なサンプルコードを以下に記載します。

#!/bin/sh

foo="hoge"

echo $foo

ここでのポイントは2点あります。

  • 変数をセットする時の=(イコール)の前後にはスペースをいれないこと
  • セットするときには、$はつけないが、展開するときには$をつけること

まず1点目は、他の言語を書き慣れている方にはかなり特殊に感じる部分だと思います。ここに半角スペースを1個でも入れてしまうとエラーが発生して、シェルスクリプトは動作しません。

次に2点目ですが、代入するときと利用するときで$をつける、つけないが変わってくるのでこれもしっかりと覚えておきましょう。

条件判定をさせる( testコマンド を使用する )

シェルスクリプトでも他の言語と同様に、ifを使って条件分岐をさせることができます。

if文で条件を記載するtest部分(スクリプト上は [ ] で記述する)部分の記述方法が特殊なのでこちらも押さえておく必要があります。

サンプルは以下のコード。

#!/bin/sh

touch TESTFILE

if [ -e TESTFILE ]

then

echo "OK"

else

echo "NG"

fi

上記は2行目でtouchしたTESTFILEというファイルが存在するかどうかをチェックして条件分岐をさせている例になります。

次は数値の比較の例を示したいと思います。

#!/bin/sh

NUM=0

if [ $NUM -eq 0 ]

then

echo "OK"

else

echo "NG"

fi

上記はNUMに代入した数値と0を比較しています。-eqという記述が数値比較となっているのが分かるかと思います。

こちらも独特な記述ですね。

testコマンドのオプションを簡単にまとめたいと思います。主に自分がよく使うものなので、一部です。

  • ファイルのチェック
-e ファイル名
指定したファイルがあれば真
-s ファイル名
指定したファイルサイズが0より大きければ真
  • 文字列チェック
-n 文字列
文字列の長さが0より大きければ真
文字列 = 文字列
2つの文字列が等しければ真
文字列 != 文字列
2つの文字列が等しくなければ真
  • 数値のチェック
数値1 -eq 数値2
2つの数値が等しければ真
数値1 -ge 数値2
1が数値2以上であれば真
数値1 -gt 数値2
数値1が数値2より大きいのであれば真
数値1 -le 数値2
数値1が数値2以下であれば真
数値1 -lt 数値2
数値1が数値2未満であれば真
数値1 -ne 数値2
2つの数値が等しくなければ真

また、先ほどの変数に代入する際の=の記述と同様にこちらも半角スペース縛りがあります。

[ 変数 -eq 数値 ]

と記述する際、[の直後と]の直前には半角スペースを入れてください。これを入れないと動作しません。

コマンドの実行結果を利用する

コマンドの実行結果を変数に代入したいときは、そのコマンドを`(バッククオート)で囲みその結果を変数に入れます。

サンプルコードは以下。

#!/bin/sh

HOSTNAME=`hostname`

echo $HOSTNAME

上記はhostnameというコマンドを一度変数に代入をしてから表示をさせている例になります。

Lockファイルを生成する

シェルスクリプトでおおいパターンがcronで定時に実行するというパターンだと思います。しかしながら、Backupの処理などある程度長時間かかることが想定される際は、2重起動をさせないようにしておく必要があります。

そういった際は、起動時にlockfileを生成し、終了時に削除をするようにし、そのファイルが存在するときは起動をさせないようにします。

サンプルコードは以下。

#!/bin/sh

LOCKFILE=/tmp/.lock

if [ -e $LOCKFILE ]

then

echo "Already Running"

else

touch $LOCKFILE

echo "OK"

sleep 10

rm $LOCKFILE

fi

上記の記述では、起動時にLockファイルをチェックしファイルが無かった場合のみに処理を実行させ最後にLockファイルを削除してLockを解除しています。

(かなり簡単すぎますか)この記述方法で2重起動を防ぐことができます。

コマンドの終了ステータスをチェックする

事前に実行したコマンドの結果によって処理を分けたいときがあると思います。そんな時は、$?をつかって事前のコマンドの終了ステータスをチェックしましょう。

サンプルコードは以下。

#!/bin/sh

host google.jp > /dev/null

echo $?

host hoge > /dev/null

echo $?

上記のコードを実行すると、下記のように表示されると思います。

0

1

これはコマンドの実行結果によって返ってくる値が変わってくるためです。

testコマンドと組み合わせて使えばば事前のコマンドの終了ステータスをとることによって条件を分岐させて、などというようなこともできます。

最後に

シェルスクリプトを書く機会は人によってはほとんど無いかもしれませんが、Unix系のサーバーを運用する人にとっては頻繁に書く機会があると思います。

ただ、個人的にはシェルスクリプトは時間はあまりかけず勢いで書いてしまうのでできるだけ下らないことではハマりたくないと思っています。

そんな時のためにこのエントリーが少しでも参考になれば幸いです。

tmiztmiz 2008/02/24 03:00 流儀によるとは思いますが、僕なら、勢いで書いたシェルスクリプトは、最初は実行権限なしのファイルで作り、シェルの引数で指定して実行するようにしておいて、精査して中身がこなれてきたら実行権限を与えるようにします

xyzxyz 2008/02/24 10:30 「testとい名前のプログラムは作っちゃいけない」がUNIXの常識だったと記憶していますが・・・

maqmaq 2008/02/24 12:21 確かに常識がどうかはわかりませんが…testというのはコマンドと重複するので
好ましくはないですね。まぁ、./testと書いているので(ry。

それより、条件分岐の例がいただけないです。touchでファイルできちゃうので
常に真側に倒れちゃうような…。

さらに、これは「かなり簡単すぎますか」と書いているのでわざと単純化してる
と思いますが、ロックファイルの例は簡略化しすぎでcronで使うには危険すぎます。

sh初心者向けなのか、何なのか対象がハッキリしませんでつ。
まぁ、はてブで人気になるエントリなのであえて苦言を…ごめんなさい。

hironohirono 2008/02/24 13:08 Lockファイル作成例は確かに単純すぎて危険。。
チェック&作成を同時に行うためにmkdirやlnでLockファイルを作るのが普通だし。
他は概ねシェルの機能の話なのに、「Lockファイルを作成する」だけ浮いてる気がします。

KoshianXKoshianX 2008/02/24 14:48 testという名前のシェルスクリプトを書いてしまうのは、常識というより多くの人がUNIXを学ぶ上で通りがちな罠ですね^^;
友人にもそれでtestコマンドを使ったシェルスクリプトが動かなくなくて数時間無駄にした人がいます

silverwiresilverwire 2008/02/24 14:51 > hostnameというコマンドを一度変数に代入をしてから
コマンド文字列ではなく、コマンドの実行結果ですね。

> シェルスクリプトを書くときに、ハマりそうな点
シングルクォートとダブルクォート、$*と$@、OSごとのコマンド
オプションの実装の違いなどにもハマりやすいかも、です。

cubickcubick 2008/02/24 14:59 カレントディレクトリのスクリプトを実行するとき、
”./” を付け忘れて小一時間ハマった記憶があります。

k2ca3k2ca3 2008/02/24 16:35 改行コードがCRLFになっていて動かなくてはまっている人を何度か見たことがある。
#!/bin/shCR
になってしまいそんなもんねえと言われる。
Windowsで書いてLinuxにFTPする時は注意。

bbbbbb 2008/02/24 18:28 正確には覚えていないですが、[ ] はbashではつかるけど、本当の素のshでは使えなかったと思います。なので、[ ]を使うぐらいなら、/bin/bashと書いては。

silverwiresilverwire 2008/02/24 19:22 > []はbashでは使えるけど、本当の素のshでは使えなかったと
Bourne Shellがおとしめられるのはちと心苦しいので。

[ ってシェルのビルトインコマンドでしたっけ? testにシンボ
リックリンクはって別名として使用している環境はあると思う
がどうだろう?

少なくとも自分の使っているUnix環境では無いですね。 AIXで
HP-UXで Linuxで Solarisで毎日shを使ってスクリプトを書い
てますが、[]で困ったことはありません。

wadapwadap 2008/02/24 21:04 > tmizさん
なるほど、自分はいつも実行権限をつけてやっていました。
でも確かに動作の確認がとれてから実行権限をつけるのもアリですね。

> xyzさん
ご指摘ありがとうございます。
「testとい名前のプログラムは作っちゃいけない」
この流儀は自分は知りませんでした・・・
確かにtestコマンドに関しても記載しているので、ここでファイル名をtestにするのはわかりづらかったと認識しています。
修正させていただきます。

> maqさん
ご指摘ありがとうございます。修正させていただきます。
修正させていただきます。
確かにこのLockファイルの例は適切でなかったと思います。

sh初心者向けに書いたのですが、中途半端な記述があったためまとまりのない文章になってしまったと感じました。

> hironoさん
ご指摘ありがとうございます。
Lockファイルの生成の方法に関しては確かに危険ですね・・・
確かにこの部分が浮いていますね。最終的に統一感のない文章になってしまいました。


> KoshianXさん
ご指摘ありがとうございます。
testというファイル名は確かに適切ではなかったと思います。

> silverwireさん
ご指摘ありがとうございます。
>> hostnameというコマンドを一度変数に代入をしてから
はい、こちらは記述ミスでした。

>> シェルスクリプトを書くときに、ハマりそうな点
確かにOSごとの細かいオプションの違いなどに関してはハマりやすい部分ですね。
その点に関しては自分もまだかけるほどわかっていないので、自分なりに理解をしてから別途書いてみたいと思います。

> cubickさん
あとから考えると自分でも悲しくなるようなミスってありますよね・・・
ただ、今回のファイル名はそれを助長してしまうような記述だった点は反省しております。

> k2ca3さん
なるほど、shをWindowsで書くという発想がありませんでした・・・
たしかにWindowsで書いてFTPでアップロードという使い方をするとおきてしまう現象かもしれません。

> bbbさん
ご指摘ありがとうございます。
少なくともいままで自分が使っている環境ではおきませんでした。
ただ、最近のディストリビューションが多いせいかもしれません。
ちょっと調べてみただけでは参考になる文献が見当たらなかったので、調べてみたいと思います。

> silverwireさん
> [ ってシェルのビルトインコマンドでしたっけ? testにシンボ
リックリンクはって別名として使用している環境はあると思う
がどうだろう?
少なくとも自分の環境ではビルドインコマンドでした。
[centos:~]$ type [
[ is a shell builtin

GoogleCodeSearchで軽くソースを眺めてみました。(36行目あたりから41行目あたりですね)
http://www.google.co.jp/codesearch?hl=en&q=show:gHLvRYPnM38:EEkmZmCW-vA:JNt2YKyind4&sa=N&ct=rd&cs_p=ftp://alpha.gnu.org/gnu/coreutils/coreutils-5.91.tar.gz&cs_f=coreutils-5.91/src/test.c&start=1

シンボリックリリンクとして使用している環境は見たことはあります。
(詳細は覚えていませんが・・・)

talastalas 2008/02/24 22:03 ロックは、test -fした後、touchするまでの間に別のプロセスが同じファイルをopen()とかcreat()してしまってると、ロックにならないという…。
やっぱり、hironoさんがおっしゃっているlnに直してたほうが良いと思います。

popcornpopcorn 2008/02/24 22:03 私にはとても嬉しい記事でした。
(つか自分で纏め記事書けよ>俺。って感じですがw)

通りすがった通りすがった 2008/02/25 05:12 testというファイル名については問題に思ったことはないですね。
昔から大事なルールはpathに’.’を含めない事じゃないでしょうか?
別にtestというファイル名が問題になるはずが無いと思うんですが。

通りすがったさんへ通りすがったさんへ 2008/02/25 08:27 じゃあ、rm とか dd とかいう名前の自作のシェルスクリプト作ってればいいと思うよ。

へたれPGへたれPG 2008/02/25 08:57 自分が書いたスクリプトを動かすホストの管理者が自分って思い込んでるバカは何なの?
スクリプトが動かされる環境なんて千差万別で環境変数なんて信じられないだろJK

envenv 2008/02/25 13:15 >変数をセットする時の=(イコール)の前後にはスペースをいれないこと
>ここに半角スペースを1個でも入れてしまうとエラーが発生して、シェルスクリプトは動作しません。

空白の入れ方によっては思いもよらぬ動作をするので注意が必要です。
LSCOLOR= ls
LANG= man who

silverwiresilverwire 2008/02/25 14:42 > [ is a shell builtin
なるほど。
ちょっと興味が湧いたので、以下の二つを調べてみました。

1. Bourne Shellで[ を使用できないことってあるのか?
2. testという名前のシェルスクリプトを実行するとどうなるか?

一つめ。

[ (test)がBourne Shell自体に組み込まれていれば、使えないこと
は無いはず。そこで、AIX, FreeBSD, HP-UX, Linux (Red Hat系),
SolarisでBourne Shellを使って調査。

AIX 5.1, 5.2, 5.3
[ もtestもシェルビルトイン。外部コマンドは /usr/bin/testが
存在。

FreeBSD 4.6.2
[ もtestもシェルビルトイン。外部コマンドは
/bin/[, test
/usr/compat/linux/usr/bin/[, test
が存在。

HP-UX 11.00, 11.11, 11.23
[ もtestもシェルビルトイン。
外部コマンドは /usr/bin/testが存在。

Linux (Red Hat, カーネルは 2.2, 2.4, 2.6系)
[ もtestもシェルビルトイン。
外部コマンドは /usr/bin/testが存在。

SunOS 5.5.1, 5.6, 5.7, 5.8, 5.9, 5,10
[ もtestもシェルビルトイン。
外部コマンドは/usr/bin/test, /usr/ucb/testが存在。
これに加え、 5.9と5,10は/usr/xpg4/bin/testも存在。

ということで、よほど初期のUnixで限り、Bourne Shellで[ が使え
ないという心配は無いと思います。

二つめ。

PATHの先頭に. (カレントディレクトリ)を追加して、カレントディ
レクトリにシェルスクリプトtestを作成。実行した結果、カレント
ディレクトリのスクリプトは実行されず、終了ステータスは 1だっ
た。(以下、testの内容。)

e.g.
#!/bin/sh -
echo ’test’

要はtestがビルトインされているシェルであれば、外部コマンドの
testよりも優先して実行されるので、カレントディレクトリのtest
は./testと指定しない限り実行できない。

なので、シェルにtestがビルトインされていなかった頃ならいざ知
らず、今や(testに関しては)そんなこともなくなった、というのが
真相かも。(でいいのかな? )

# 長文すみません。抜けがあれば指摘してもらえると嬉しいです。

infernoinferno 2008/02/25 15:04 ロックファイル作るなら、せめてtrapコマンドで削除を保証しないと永遠に実行されないスクリプトになりますよ。

teramakoteramako 2008/02/26 10:06 >id:silverwire さん
抜けというか各UNIX,Linuxで/bin/shがBourneShellであることは確かめましたか?
AIXの場合 /bin/sh は /bin/ksh のハードリンクになっているのではないかと思います。
Linuxもディストリビューションによってはbashへリンクしているものもあるのではないかと...

bitmapbitmap 2008/02/26 12:55 [ ですが、少なくとも’96の頃から使えています。
私が始めて触った SunOS 4.x の頃のはずで、この頃から使えてた記憶です。
ただし、test への ln -s だったかは確かではありません。

ちなみに、手元にある『プロフェッショナル シェルプログラミング』なる本(1996年4月初版)にも if [ ] は載っています。が本には bash も載ってるからビルドインの可能性は否定しません。
さらに探してみたところ、『ツールとしてのUNIX』(1993年4月初版)にも if [ ] は載っていました。/bin/sh が何を想定しているかは推定できないのですが、WS (SYSTEM V)前提、xinit 後に twm を「別に、コマンドを入力して」立ち上げるなどの説明がある、などから推定してください。あと、この本は sh と csh,tcsh しか触れていません。
他には、オライリー『sendmail 解説』(日本語翻訳:1994年8月初版)にも if [ ] は載っています。翻訳なので、おそらく英語版にも同じスクリプトはあると思われます。ちなみに、現在売ってる二冊分冊形式になる前の大きさ小さめで分厚いサイコロ本の方です。
手元の書籍でさかのぼれるのはこれくらいですが、やはり10年以上前から if [ ] は普通に利用できるように認識されています。

個人的見解。
一番最初の BourneShell は [ コマンドが「ビルドインではない」は確かに正しいのですが、外部コマンドとして用意されていれば問題ないので同時に /usr/bin/[ というコマンドが普及していなかったと言えないのであれば、このままで問題ないと思われます。で、これについては前述の通り、技術系の本に載っていることから「普及していない」というのは、もっとさかのぼらないと言えないかと思われます。

それから手元で触れるマシンをチェックしたところ
SunOS5.8 : BourneShell(補完もヒストリも無い漢仕様) 、 ビルドイン、[ コマンドは無し
Vine 3.1:bash ソフトリンク、ビルドイン、/usr/bin/[ あり /usr/bin/test ソフトリンク
SuSE Linux 10.2 : bash ソフトリンク、ビルドイン、/usr/bin/[ あり、/usr/bin/test とは別の様子(ファイルサイズ違い)
NetBSD 2.0 : BourneShell (詳細は他人のマシンなので調べてない、ksh が別にありだが完全に別プログラム)、ビルドイン、/bin/[ あり、/bin/test ハードリンク
という結果でした。

ご参考までに

まとめるとまとめると 2008/02/26 13:46 素の(コンパニオンCDから追加しない)Solaris8では条件判定に[が使えません。潔くtestコマンドを使うか、bashなりをインストールして、shebangは/bin/bash(とか/usr/sfw/bin/bashとか)を指定しましょうって事でいいんじゃないすか?
今のSolarisでも試してみようかな。rootのデフォルトシェルはbashじゃなかった気がするし。

まとめるとまとめると 2008/02/26 23:52 「Solaris Express Developer Edition 1/08 snv_79b X86」で確認した結果は以下。
rootのデフォルトシェルは/sbin/sh(実行可能ファイル。補完もhistoryもないのでbashではない)。
/usr/bin/test 実行可能ファイル(134バイト)
/usr/bin/[ 実行可能ファイル(31640バイト)
という結果でした。ご参考まで。

wadapwadap 2008/02/27 01:26 皆様、多くのコメントありがとうございます。
かなりの数になってきたので、別エントリーをたてて正式にコメントをさせていただきたいと思います。

bitmapbitmap 2008/02/27 14:06 私の書き方が悪かったのですね。

Solaris (記憶の上では Solaris と呼ばれる前の SunOS 4.x の頃から。Solaris は SunOS 5.2 から Solaris 2 じゃなかったかな?)でも /bin/sh スクリプト内で条件判定に [ は使えます。bash を指定する必要はありません。『/bin/sh にビルドインとして [ が入っています』から。
ただし、少なくとも手元でチェックできたほかの UNIX 系のように、『外部プログラムとして別途用意はされていない』様子です。

[ が『/bin/sh にビルドインとして用意されている』 or 『パス中に外部コマンドとして用意されている』の「どちらかが満たされている」場合に、/bin/sh スクリプトで利用できます。
そして手元の調査では Solaris 8 ( SunOS 5.8 ) でも『ビルドイン』が用意されている……という意味で書いたのですが、そう読まれなかったようで。

以上。Solaris 8 でも別に bash なり ksh なりを用意せずとも、/bin/sh だけで [ を問題なく利用できます。

一つ訂正。
Solaris8 の /bin/sh ですが、ハードリンクでした。
共有しているのは /bin/jsh , /bin/pfsh ,後一つ不明、なんですが、どちらも聞いたことが無いシェル……





私の書き方が悪かったのですね。

Solaris (記憶の上では Solaris と呼ばれる前の SunOS 4.x の頃から。Solaris は SunOS 5.2 から Solaris 2 じゃなかったかな?)でも /bin/sh スクリプト内で条件判定に [ は使えます。bash を指定する必要はありません。『/bin/sh にビルドインとして [ が入っています』から。
ただし、少なくとも手元でチェックできたほかの UNIX 系のように、『外部プログラムとして別途用意はされていない』です。

[ が『/bin/sh にビルドインとして用意されている』 or 『パス中に外部コマンドとして用意されている』の「どちらかが満たされている」場合に、/bin/sh スクリプトで利用できます。
そして手元の調査では Solaris 8 ( SunOS 5.8 ) でも『ビルドイン』が用意されている……という意味で書いたのですが、そう読まれなかったようで。

以上。Solaris 8 でも別に bash なり ksh なりを用意せずとも、/bin/sh だけで [ を問題なく利用できます。

一つ訂正。
Solaris8 の /bin/sh ですが、ハードリンクでした。
共有しているのは /bin/jsh , /bin/pfsh ,後一つ不明、なんですが、どちらも聞いたことが無いシェル……

silverwiresilverwire 2008/02/28 00:41 あ、すでにエントリーに区切りがついていたようですね。

teramakoさんより指摘をいただいたので、もう少し調べていたの
ですが、それについてはいずれまた機会があればということで。

edry(えどりぃ)edry(えどりぃ) 2008/03/01 00:25 初めてシェルスクリプトを書くとき...

sh -x ./edry.sh

とか覚えてると、幸せかも。

echoecho 2008/03/01 02:38 初めてシェルスクリプトをデバッグする時...

set -xv
(処理)
set +xv

とか覚えておくと、幸せかも?

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証