シェルスクリプトの基礎 - シグナルとtrapコマンド
概要
シグナルとは、プロセス間で通信を行うための仕組みであり、Linuxカーネルに実装されている。
実行中のプロセスの処理を一時的に止めて、別のプロセスを処理する場合等に使用される。
シグナルは、キーボードからの割り込み(SIGINT)や浮動小数点例外(SIGFPE)、プロセスの終了(SIGTERM)等、30種類以上ある。
これらのシグナルは、プログラム(プロセス)自身の動作によって発生する同期シグナル、外的な要因で発生する非同期シグナルの2種類ある。
シグナルの動作を以下に示す。
- あるプロセスの実行中において、処理を割り込ませたい場合、カーネルがシグナルを発生させる。
- シグナルが発生すると実行中のプロセスが中断され,特定の処理が実行される。
この特定の処理のことを、シグナルハンドラと呼ぶ。
シグナルは突然発生するため、発生するシグナルごとに実行する処理を、予めシグナルハンドラとして登録する。
使用可能なシグナルの一覧は、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 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 35) SIGRTMIN 36) SIGRTMIN+1 ...略
シグナルを送出する
プロセスにシグナルを送出するには、killコマンドを使用する。
以下のように、killコマンドのオプションとして、送出先プロセスのプロセスIDと送出するシグナルを指定して実行する。
kill [シグナル番号|シグナル名] PID
シグナルを送出するには、killコマンドに送出先プロセスのプロセスIDと送出するシグナルを指定して実行する。
シグナルの指定には、各シグナルに定義されているシグナル番号またはシグナル名のいずれかを使用する。
送出先プロセスのプロセスID(PID)は、psコマンドやpgrepコマンドで確認することができる。
シグナル番号またはシグナル名を省略する場合、標準で15番シグナル(TERM)が送出される。
sleep 100000 & # sleepコマンドに&を付加してバックグラウンドで実行する [1] 5355
pgrep -lf sleep # pgrepコマンドでプロセスIDを検索する 5355 sleep 100000
kill -15 5355 # TERMシグナルを送出する
pgrep -lf sleep # 再検索すると終了していることを確認できる [1]+ 終了しました sleep 100000
下表に、各シグナルが持っている意味を示す。
表. 一般的なシグナル番号一覧
シグナル番号 | シグナルが持つ意味 |
---|---|
0 | プロセス終了時に、プロセスが自分自身に対して送出するEXITシグナル。 |
1 | XWindowのクローズやデーモンのリセットに使用されるハングアップシグナル。 |
2 | [Ctrl]+[C]キーや[Delete]キーを押下した時に発生する割り込みシグナル。 |
3 | [Ctrl]+[\]キーを押下した時に発生するクイットシグナル。 |
9 | プロセスを強制終了するためのキルシグナル。 強制終了であるため、trapできない。 |
15 | プロセスを終了させるための終了シグナル。 killコマンドは標準でこのシグナルを使用する。 ( kill <PID> は、kill -15 <PID> と同じ意味)
|
表. 一般的なシグナル名一覧
シグナル番号 | シグナル名 | 通知内容 |
---|---|---|
1 | HUP | プロセスに再起動を通知する。 |
2 | INT | プロセスに割り込みを通知する。 [Ctrl]+[C]キー |
3 | QUIT | プロセスに終了を通知する。 (coreを作成する) |
9 | KILL | プロセスに強制終了を通知する。 |
15 | TERM | プロセスに終了を通知する。 (デフォルト) |
18 | CONT | プロセスに再開を通知する。 |
19 | STOP | プロセスに中断を通知する。 |
20 | TSTP | プロセスにサスペンドを通知する。 [Ctrl]+[Z]キー |
trap コマンド
trapコマンドの使用方法
trap
コマンドは、送出されたシグナルを捕捉して、予め指定された処理を実行するコマンドである。
trap "<コマンド>" <シグナルリスト>
実行中のシェルスクリプトに対して送出されたシグナルは、trap
コマンドを使用することで捕捉することができる。
kill
コマンド等により、シグナルリストに指定されたシグナルが送出されると、trap
コマンドはそれを捕捉して、指定したコマンドを実行する。
trap
コマンドを使用することにより、各シグナルの規定の動作を置き換えることができる。
ただし、強制終了のシグナルである9番はtrapすることはできないため、注意すること。
trap "echo trapped." 2 # ここで、[Ctrl]+[C]キーを押下する trapped.
trap処理のリセット
trap
コマンドでシグナル捕捉時に実行するように指定した動作は、リセットを行うことで解除することができる。
trap
コマンドにシグナルリストのみを指定して、指定されたシグナルの捕捉処理をリセットする。
trap <シグナルリスト>
trap
コマンドで指定した処理をリセットする場合(例: trap "rm -f *.tmp; exit 1" 1 2 3 15
)、
各シグナルの標準の動作に戻すには、trap 1 2 3 15
のように、リセットするシグナルのみを指定して実行する。
trap
コマンドの処理を指定した部分に何も記述しないことにより、シグナル受信時に標準の動作に戻すことができる。
※注意
ただし、trap "" 1 2 3 15
と記述した時、シグナルを捕捉した場合は""を実行する。
これは何も処理をしないという意味になり、結果として、該当するシグナルを無視するという設定になってしまう。
trap 'echo trapped.' 2 # ここで、[Ctrl]+[C]キーを押下する trapped. # trapコマンドに指定した処理が実行される trap 2 # trapコマンドの設定をリセットする # ここで、[Ctrl]+[C]キーを押下する # 指定した処理が解除されているのが確認できる
trapコマンドの応用例 1
trapコマンドをシェルスクリプトに組み込むことで、
シグナル受信により実行途中で終了する場合に行う終了処理を指定することができる。
trap 'command' 1 2 3 15 # 通常の用途であれば、trapするシグナルは1, 2 , 3, 15のみでよい
シェルスクリプト内で受け取る可能性があるシグナルには9番(SIGKILL
)もあるが、9番(SIGKILL
)は指定してもtrapすることができない。
したがって、シェルスクリプト内でtrap処理を行う場合は、1、2、3、15のみを指定する。
以下の例では、trap
コマンドを使用して、終了前にゴミ掃除を実行している。
実行中のシェルスクリプトを割り込み等により途中で終了した場合、スクリプト中で作成した一時ファイルが残ってしまうことがある。
このような一時ファイルを残さないようにするため、trap
コマンドを使用して、終了前にゴミ掃除を実行する。
#!/usr/bin/env sh
# ゴミ掃除用のtrap処理を指定する
trap 'echo "trapped."; rm -f temp.$$; exit 1' 1 2 3 15
# trap時に削除するファイルを作成する。
echo "Trap Test." > temp.$$
echo "作成されたファイルを確認する"
ls -l temp.$$
# [Ctrl]+[C]キーを押下するまでループ
echo "[Ctrl]+[C]キーを押下してください"
while :
do
:
done
# 出力
作成されたファイルを確認する
-rw-r--r-- 1 sunone sunone 11 6月 2日 14:01 temp.9535
[Ctrl]+[C]キーを押下してください
# ここで、[Ctrl]+[C]キーを押下する
trapped. # トラップ処理が実行されて、一時ファイルが削除される
trapコマンドの応用例 2
EXIT
シグナルをtrapして、シェルスクリプト終了時に必ず実行される処理を指定する。
trap 'command' EXIT # シェルスクリプト終了時に送信される0番のシグナル(EXIT)をtrapする
シェルスクリプトが終了する時に終了メッセージを表示するような処理は、予め、EXIT
シグナルをtrapすることで実現できる。
また、trap '<コマンド>' 0
のように0番を指定しても結果は同じになるが、
trap処理が終了処理であることが明示的に分かるEXIT
を指定する方がよい。
以下の例では、EXIT
シグナルをtrapして終了処理を実行している。
このテクニックは、exit
コマンドを実行する分岐が複数存在して、かつ、終了時に共通の処理を行う必要がある場合において非常に有効である。
#!/usr/bin/env sh
# EXITシグナルをtrapして終了メッセージを指定する
trap "echo '`basename $0`を終了します'" EXIT
# 他のシグナルもtrapしておく
trap "echo '他のシグナルをtrapしました'" 1 2 3 15
echo "[Ctrl]+[C]キーを押下してください"
# [Ctrl]+[C]キーで終了するため、sleepする
sleep 10
exit 0
# 出力
[Ctrl]+[C]キーを押下してください
# ここで、[Ctrl]+[C]キーを押下する
他のシグナルをtrapしました
xxx.shを終了します # INTシグナルで終了した場合でも、EXITシグナルはtrapできる