bash の signal handler(trap)の動きを、サンプルシェルで学びます。
- bash の signal handler (trap)を学ぶ
- signal handler 処理 と bash 組込み関数(read)
bash version
$ env LANG=C bash --version
GNU bash, version 5.1.8(1)-release (x86_64-redhat-linux-gnu)
Operationg system
# hostnamectl
Operating System: Rocky Linux 9.6 (Blue Onyx)
Kernel: Linux 5.14.0-570.23.1.el9_6.x86_64
Architecture: x86-64
メイン処理 s1.sh
#!/bin/bash
# signal handler の 引数で渡される signal番号を記憶
SIGNAL_CATCH=0
# --------------------------------------------
# SIGNAL処理
# --------------------------------------------
function __f_signal()
{
echo "[ signal ] $1 $2"
pstree -pA $$
SIGNAL_CATCH=$1
}
trap '__f_signal 1 HUP' 1
trap '__f_signal 2 INT' 2
trap '__f_signal 3 QUIT' 3
# --------------------------------------------
# MAIN (s1.sh)
# --------------------------------------------
RC=0
echo "PID-A( $$ )"
./s2.sh
RC=$?
[[ $SIGNAL_CATCH -ne 0 ]] && (
echo
echo "SIGNAL 検知 ( $SIGNAL_CATCH )"
echo
)
exit $RC
s1.sh から 呼ばれる s2.sh
#!/bin/bash
# 組込み関数(read)内での signal 処理の為に, READ中フラグを利用する
READ_F=0
# ----------------------------------------------------
# s1.sh から (別プロセスとして)呼び出される場合
# s2.sh 側の signal handler が動く
# ----------------------------------------------------
function __f_signal2()
{
echo "[ signal2 ] $1 $2"
echo "[ $0 ]( $$ )"
pstree -plA $$
[[ $READ_F -eq 1 ]] && exit $(( 128 + $1 ))
}
trap '__f_signal2 1 HUP' 1
trap '__f_signal2 2 INT' 2
trap '__f_signal2 3 QUIT' 3
# ----------------------------------------------------
# 組込み関数の read を 利用する前に READ_F変数に「1」をセットする
# ----------------------------------------------------
function __f_read()
{
local RC=0
READ_F=1
read $@
RC=$?
READ_F=0
return $RC
}
# ----------------------------------------------------
# MAIN ( s2.sh )
# ----------------------------------------------------
RC=0
echo "PID-B( $$ ) $(basename $0)"
pstree -pA $$
# read 関数で TIMEOUT=10秒
__f_read -e -t 10 -i TEST -p " -> " ANS
RC=$?
echo "read rc($RC) ANS[$ANS]"
# read を 利用しない場合, sleep() で, 10秒 WAIT
# sleep 10 &
# wait
# RC=$?
# echo "read wait($RC) ANS[$ANS]"
[[ -n "$ANS" ]] && echo "$ANS"
exit $RC
【実行サンプル】 readで停止中に CTL+C (SIGINT)を行う
$ ./s1.sh
PID-A( 985217 )
PID-B( 985218 ) s2.sh
s2.sh(985218)---pstree(985220)
->TEST
[ signal2 ] 2 INT
[ ./s2.sh ]( 985218 )
s2.sh(985218)---pstree(985223)
[ signal ] 2 INT
s1.sh(985217)---pstree(985224)
SIGNAL 検知 ( 2 )
- s2.sh の read() で, timeout(10秒)中に CTL+C を実行
入力プロンプト「->TEST」が,表示されているタイミングで, CTL+C を入力 - s2.sh の signal handler / __f_signal2() が動く
- __f_signal2() 内では、環境変数「READ_F」が、「1」の時
exit $(( 128 + $1 )) で終了 - s1.sh へ処理が戻るが、SIGINT を 検知した為,
s1.sh の __f_signal() が, 実行される。
- s1.sh と s2.sh は、別プロセスの為, それぞれの signal handler が, 動きます。
- s1.sh は, s2.sh の終了を待ち合わせている為, signal handler は、s2.sh の処理が終了し, s1.shへ処理が戻った後, s1.sh の handler が実行されます。
- bash 組込み関数 read() が, 入力待ち状態の場合, exit() を, 実行しない回切り、処理を打ち切ることができません。
たとえば、__f_signal2() 内で、exit せず、そのまま signal handler が終了した場合, read() 関数内に処理が戻り、s2.sh の場合では、読み込み待ちに戻ります。 - s1.sh は、signal handler 関数 __f_signal() で、s1.sh を終了させる(exit()を呼ぶなど)処理は行っていない為, ハンドリング後, s2.sh を呼び出した, 次の処理へ, 処理が継続されます。
bash による イベント駆動処理の作成
- signal 検知の際に, 当該シェルを終了させるようなプログラミングが一般的だと思います。
- s1.sh では、handler では、exit を 行っていない為、SIGINT検知しても, 何事もなかったかのように継続されます。
signal handler と read() を, 組み合わせ, 無限ループ処理により, イベント駆動型のプログラミングが可能になります。
また、read() を, __f_read() の 様に オーバーライド関数を使うことで, read() 中に, signal 検知されたかなどを, 検知させることも可能になります。 - この様な方法で、bash シェルにより イベント駆動&対話型 の シェルスクリプトを作成可能です。
- SIGHUP も trap 可能ですので、SIGHUP検知により, 設定情報を読み込み直すなどの処理を記述可能になります。