bashのtrapがSIGTERMを拾えない?


bashの機能の1つにtrapというものがあります。これは割込みのシグナルを受け取った時の動作を指定する事ができるものなのですが、先日これを使おうとしたらうまく動かないという事がありました。

SIGTERMを受け取ったら止める

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の行をコメントして動かしてみます。

#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はコマンド実行中の割込みはできない

上記の例から、「trapはコマンド実行中に受け取ったシグナルはそれが終わるまで待たなければ行けない」という事のようです。

sleep 1 ←×
  ↓    ←〇
sleep 1 ←×
  ↓    ←〇     〇=trap可能
(以下略)         ×=trap不可

バッググラウンドに回してwaitの活用

プログラムによっては細切れにできないものもあると思います。そんな時はバックグラウンドに回してwaitを活用するという方法もあります。

#!/bin/bash
trap 'echo IN_TRAP; exit 1' SIGTERM
sleep 600 &
wait

これなら常時SIGTERMを受け取れます(くどいようですが終了してもsleepは生き続けます)

疑似シグナルEXITを使う

割込みを入れるシグナルが単に終了した時だけで十分な場合疑似シグナルという仕組みを利用するのも手です。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を使ってる人が多いですねぇ。やる事次第ですが、終了の割込み以外ってあんまり使わないですもんねぇ。。