2009-03-07
シェルスクリプトでシグナルハンドラを書く
シェルスクリプト | |
シェルスクリプトでシグナルをトラップするにはtrapを使います。
最初の引数が実行コマンドで、その後ろにトラップするシグナルを並べます。
#!/bin/sh trap 'echo exit...' EXIT trap 'echo trap INT or TERM' INT TERM echo start sleep 5 echo end
出力
start end exit...
start (^C押す) trap INT or TERM end exit...
シグナル指定は名前でも番号でもよくて、他のシグナルはtrap -lやkill -lで見れます。
$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT ...
それでは応用例を3つほど。
HUPシグナルで設定ファイルのリロード
hup_handle.sh
#!/bin/sh startup () { . ./env.sh } trap startup HUP startup while :;do echo $STR sleep 2 done
env.sh
STR=AAA
実行
$ ./hup_handle.sh AAA AAA ...
別ターミナルで設定ファイルを書き換えてHUPシグナルを飛ばしてやれば、
$ echo STR=BBB > env.sh $ pkill -HUP hup_handle.sh
設定ファイルがリロードされて出力が変わります。
... AAA AAA BBB BBB ...
終了処理
例えば終了処理としてテンポラリファイルの削除とメッセージの表示を行うには。
#!/bin/sh end () { rm -f *tmp.$$ echo bye... } trap end EXIT #trap 'exit 255' 1 2 3 15 date > date.tmp.$$ echo start cat date.tmp.$$ sleep 10 echo end
テンポラリファイルに$$(PID)をつけてるのは複数同時に起動されたときにごっちゃにならないようにするためです。
2重起動防止
同じスクリプトを複数同時に起動できないようにするにロックファイルで制御するには。
#!/bin/sh lockfile=lockfile if [ -f $lockfile ];then echo $0 is locked exit 100 fi touch $lockfile trap 'rm -f $lockfile' EXIT #trap 'exit 255' 1 2 3 15 echo start sleep 10 echo end
既に他で起動されてると怒られます。
$ ./lock.sh ./lock.sh is locked
2009-03-06
配置に依存しないスクリプト
シェルスクリプト | |
スクリプトがごちゃごちゃしてきたので
#!/bin/sh VAR=ABC echo $VAR
$ ./try.sh ABC
bin、etc、(log、tmp、...)とディレクトリを分けたら、
$ tree try/ try/ |-- bin | `-- try.sh `-- etc `-- try.env $ cat try/etc/try.env VAR=ABC
binに移動しないとちゃんと動作しなくなったとか
#!/bin/sh . ../etc/try.env echo $VAR
$ ./try.sh ABC $ cd .. $ ./bin/try.sh ./bin/try.sh: line 2: ../etc/try.env: そのようなファイルやディレクトリはありません
配置を変えたらスクリプトも変えないといけなくなったとか
#!/bin/sh SCRIPT_DIR=/home/mikeda/try . $SCRIPT_DIR/etc/try.env echo $VAR
$ ~/try/bin/try.sh ABC $ mv ~/try /tmp $ /tmp/try/bin/try.sh /tmp/try/bin/try.sh: line 3: /home/mikeda/try/etc/try.env: そのようなファイルやディレクトリはありません
そういうのはちょっとさみしい。
これは$0とdirnameを使えば対応できます。$0には0番目の引数、つまり実行スクリプトのパスが入ってるのでそれを使えばbinディレクトリの位置がわかるからです。
#!/bin/sh cd `dirname $0` . ../etc/try.env echo $VAR
#!/bin/sh SCRIPT_DIR=`dirname $0`/.. . $SCRIPT_DIR/etc/try.env echo $VAR
$ ~/try/bin/try.sh ABC $ mv ~/try /tmp $ /tmp/try/bin/try.sh ABC
2009-02-21
echoの移植性のなさ(改行抑止)
コマンドの違いはtarのzオプション、gzgrepとzgrep、findのコマゴマした仕様など、いろいろあるんですが意外なところではechoの改行抑止があります。
先日、Linuxで動いていた/bin/shスクリプトがSolarisではちゃんと動かない、ということがあり、原因を調べたら次の1行でした。
echo -n `date +%s`
この1行で、Linuxだと改行なしのタイムスタンプ、例えばが「1235256375」、が表示されますが、Solarisだと「-n 1235256375<改行>」という全く違う出力が得られます。
echoの改行抑止の方法は2種類あり、LinuxとSolarisでは挙動が変わってきます。さらにややこしいのはただechoと打ったときに呼び出されるのはシェルの内部コマンドのechoであり、/bin/echoとは別物だということです。
Linux
とにかく-n
Solaris
/bin/shのecho、/bin/echoは\c
bashのechoは-n
sh $ echo 'ABC\c' bash $ echo -n ABC bash $ /bin/echo 'ABC\c'
移植性を確保する簡単な方法はprintfに書き換えることです。
printf ABC
上記のスクリプトもそのように修正しました。
2009-02-18
シェルスクリプトでsplit
シェルスクリプト | |
シェルスクリプトで文字列分割と言えばやはりwhile read
while read f1 f2 f3;do echo $f1 echo $f2 done < file.txt
標準入力やファイルから1行ずつ読み込んで、という最もありがちな処理にはこれが1番わかりやすいですね。IFSで区切り文字を変えたりもできます。
しかしある変数、STRに入った文字列を分割するにはどうでしょうか。
readではちょっと難しいです。
STR="aa bb cc" echo $STR | read f1 f2 f3 echo $f1 # => 何も表示されない
これはパイプがこっそり右側のコマンドをサブシェル起動するためです。
(と昔どこかで読んだんですが$$を表示してみるとPIDはいっしょですね。別の理由かも)
一連の処理をかっこでくくれば回避できなくもないですが、使い勝手悪すぎですね。
STR="aa bb cc" echo $STR | ( read f1 f2 f3 echo $f1 echo $f2 )
これにはいくつかやりかたがあります。
配列代入
STR="aa bb cc" foo=($STR) echo ${foo[0]} echo ${foo[1]}
特殊変数、$1...に代入
STR="aa bb cc" set -- $STR echo $1 echo $2
bash限定、名前つきパイプ
STR="aa bb cc" read f1 f2 f3 < <( echo $STR ) echo $f1 echo $f2
2009-01-09
シェルでサイコロをふる
シェルスクリプト | |
シェルスクリプトで乱数を得るにはRANDOM変数を参照しましょう。
$ expr $RANDOM % 6 + 1 3
使用例として毎秒5種類のメッセージのどれかをシスログ出力するスクリプト。
while :;do logger -p local5.debug $( sed -n `expr $RANDOM % 5 + 1`p <<END message1 message2 message3 message4 message5 END ) sleep 1 done
絶対だれも使わないな。

