bashの機能の1つにtrapというものがあります。これは割込みのシグナルを受け取った時の動作を指定する事ができるものなのですが、先日これを使おうとしたらうまく動かないという事がありました。
trapでは受け取ったシグナルに応じて書く事ができます。例えば下記はSIGTERMを受信したらプログラムを終了するように書いてみます。
# vi trap_test.sh
#!/bin/bash -x trap 'echo IN_TRAP; exit 1' SIGTERM sleep 600
これを実行します。
# ./trap_test.sh + trap 'echo IN_TRAP; exit 1' SIGTERM + sleep 600
この状態でSIGTERMシグナルを送ります。
# ps aux | grep trap_test | grep -v grep root 32293 0.0 0.0 12700 3020 pts/0 S+ 12:46 0:00 /bin/bash -x ./trap_test.sh # kill -SIGTERM 32293
で、スクリプトの方はどうなってるかというと・・・
# ./trap_test.sh + trap 'echo IN_TRAP; exit 1' SIGTERM + sleep 600
うんともすんとも言いません・・・・なぜ・・・・・・
ちなみにSIGTERM以外にSIGINTやUSR1なども試しましたがどのシグナルも同じでした。
先ほどのスクリプトのtrapの行をコメントして動かしてみます。
#trap 'echo IN_TRAP; exit 1' SIGTERM
この状態で実行して、先ほどと同じようにSIGTERMを送ると
# ps aux | grep trap_test | grep -v grep
root 32341 0.0 0.0 12700 3060 pts/0 S+ 12:50 0:00 /bin/bash -x ./trap_test.sh
# kill 32341
# ./trap_test.sh + sleep 600 Terminated [root@ryzen ~]#
今度は正常に終了しました。
ちなみに本題とは関係ないですが、終了したのはシェルスクリプトのみで、裏ではsleepが動き続けています。
# ps aux | grep sleep root 32342 0.0 0.0 7284 828 pts/0 S 12:50 0:00 sleep 600
色々調べて、原因がわかったのでそれを確認する為にちょっと改良。
これは単に1秒おきにsleepするだけのスクリプトです。
#!/bin/bash trap 'echo IN_TRAP; exit 1' SIGTERM CNT=0 while : do echo "CNT=$CNT" CNT=$((CNT+1)) sleep 1 done
実行します。
# ./trap_test.sh CNT=0 CNT=1 CNT=2 ....(略)
で、この状態でSIGTERMを送ってみます。
# ./trap_test.sh CNT=0 (略) CNT=9 IN_TRAP
動いた!!!動いたよ!!!!
上記の例から、「trapはコマンド実行中に受け取ったシグナルはそれが終わるまで待たなければ行けない」という事のようです。
sleep 1 ←× ↓ ←〇 sleep 1 ←× ↓ ←〇 〇=trap可能 (以下略) ×=trap不可
プログラムによっては細切れにできないものもあると思います。そんな時はバックグラウンドに回してwaitを活用するという方法もあります。
#!/bin/bash trap 'echo IN_TRAP; exit 1' SIGTERM sleep 600 & wait
これなら常時SIGTERMを受け取れます(くどいようですが終了してもsleepは生き続けます)
割込みを入れるシグナルが単に終了した時だけで十分な場合疑似シグナルという仕組みを利用するのも手です。trapでは疑似シグナルと言ってシステムのシグナルとは別にtrap独自のシグナルが用意されています。何種類かあるのですが、そのうちEXITというシグナルががあるので紹介します。
このEXITシグナルは「スクリプトが終了された」という意味になります。これは先ほどのSIGTERMなどとは違い、スクリプトから生成されたプロセスの状態に関係なく終了すればこの割込みが発生します。
試しに下記のスクリプトを作ってみます。
# vi trap_test.sh
#!/bin/bash trap 'echo IN_TRAP' EXIT sleep 600
実行します。
# ./trap_test.sh
SIGTERMなどで終了させます。
# ps aux | grep trap_test | grep -v grep root 414 0.0 0.0 12700 3048 pts/0 S+ 13:34 0:00 /bin/bash ./trap_test.sh # kill -SIGTERM 414
するとスクリプトは終了され、しかもtrapが実行されています。
# ./trap_test.sh IN_TRAP Terminated
ネットで検索してる限り、疑似シグナルEXITを使ってる人が多いですねぇ。やる事次第ですが、終了の割込み以外ってあんまり使わないですもんねぇ。。