ブログトップ 記事一覧 ログイン 無料ブログ開設

IT 東京 楽しいと思うこと

2009-03-07

シェルスクリプトでシグナルハンドラを書く

| 00:36 |

シェルスクリプトでシグナルをトラップするには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...


EXITプロセス終了時に投げられる番号0のシグナルです。

シグナル指定は名前でも番号でもよくて、他のシグナルは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

配置に依存しないスクリプト

| 01:03 |

スクリプトがごちゃごちゃしてきたので

#!/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の移植性のなさ(改行抑止)

| 06:56 |

Redhat系のLinuxSolarisをよく使います。

コマンドの違いはtarのzオプション、gzgrepとzgrep、findのコマゴマした仕様など、いろいろあるんですが意外なところではechoの改行抑止があります。


先日、Linuxで動いていた/bin/shスクリプトSolarisではちゃんと動かない、ということがあり、原因を調べたら次の1行でした。

echo -n `date +%s`

この1行で、Linuxだと改行なしのタイムスタンプ、例えばが「1235256375」、が表示されますが、Solarisだと「-n 1235256375<改行>」という全く違う出力が得られます。


echoの改行抑止の方法は2種類あり、LinuxSolarisでは挙動が変わってきます。さらにややこしいのはただ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

| 06:11 |

シェルスクリプト文字列分割と言えばやはり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

シェルでサイコロをふる

| 04:37 |

シェルスクリプトで乱数を得るには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

絶対だれも使わないな。