Bashシェルスクリプトで終了前に後始末をさせたい時はtrapコマンドを活用しよう
はじめに
Bash シェルスクリプトの終了時に、 必ず後処理をさせたい 場合があります。
例えばスクリプト内で作成した 一時ファイルを削除する といった場合です。
そんな時に使えるコマンドとして、Bash ビルトインコマンドである trap
コマンドがあります。
trap
コマンドを使ってスクリプト終了前に後処理をさせる方法について紹介してみたいと思います。
trap
コマンドの使い方
trap
コマンドの主な引数は以下の 2 つになります。
- 実行させたいシェルスクリプトコマンド
- トラップを仕掛けたい シグナル
突然出てきましたが シグナル とは何でしょう?
シグナルとは?
シグナルとは、 プロセスとプロセスの間で通信を行う際に使用される信号 のことです。
ターミナル上で実行中のコマンドを {ctrl}
+ {C}
で停止させた、といった場合を例に上げましょう。
この場合も「一方のプロセス」に対して「他方のプロセス」がシグナルを送った結果、実行中のコマンドが停止されます。
- 一方 は実行中のコマンドプロセス
- 他方 はユーザーにより実行されるショートカットキー操作 (
{ctrl}
+{C}
)
このように、シグナルを受け取ったプロセスの状態は、通常の処理実行から変化します。
文面だけではわかりにくいため、 trap
コマンドを使った具体的な例をみながらシグナルについても理解していきましょう。
trap
コマンドでシグナルをトラップする
まずは以下のような簡単なスクリプトを作成します。
test.sh
#!/usr/bin/env bash
trap 'echo "trap SIGINT"' SIGINT
cat
trap
コマンドは 2 つの引数を受け取っています。
第 2 引数で指定した SIGINT ( 後述 ) というシグナルをトラップします。
トラップに成功すると、第 1 引数のコマンドを実行します。
trap
コマンドを実行したとしても、処理の流れはすぐ次の行に進みます。
cat
コマンドは単体で利用された場合、ユーザーの入力を永遠に待ち続けます。
結果、このスクリプトは最終行の cat
実行タイミングから先に進みません。
作成したスクリプトを実行します
# スクリプト実行
$ ./test.sh
cat
コマンドのおかげで、スクリプトが実行中のまま終了しません。
{ctrl}
+ {c}
キーを押して、 SIGINT シグナルを送信する
実行中のスクリプトに対して SIGINT シグナルを送信してみます。
SIGINT シグナルは、実行中のプロセスに対して {ctrl}
+ {c}
キーを押すことで送信できます。
試してみます。
$ ./test.sh
^Ctrap SIGINT
通常、 {ctrl}
+ {c}
キーを押すと実行中のスクリプトが中断されるだけですが、今回は trap SIGINT という文字列が出力されています。
{ctrl}
+ {c}
キーが押されたタイミングで ./test.sh
スクリプトにシグナルが送信された結果 SIGINT シグナルがトラップされ 、 trap
の第 1 引数のコマンドが実行されたことがわかります。
kill
コマンドで SIGINT シグナル以外 を送信する
今度は SIGINT シグナル以外 のシグナルを送信してみます。
もう一度同じスクリプトを実行した後、別ターミナルに開き、実行中のプロセスを一覧表示します。
# 実行中プロセス一覧
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 11840 2896 pts/0 Ss 11:09 0:00 /bin/bash
root 18 0.0 0.0 11840 2888 pts/1 Ss 11:09 0:00 bash
root 112 0.0 0.0 11700 2484 pts/0 S+ 12:47 0:00 bash ./test.sh
root 113 0.0 0.0 4396 744 pts/0 S+ 12:47 0:00 cat
root 115 0.0 0.0 51744 3352 pts/1 R+ 12:47 0:00 ps aux
スクリプト実行プロセス ( bash ./test.sh
) が見つかります。
このプロセスに対して kill
コマンドで シグナル を送信してみます。kill
は 指定されたプロセス に対して SIGTERM シグナル を送信するコマンドです。
$ kill 112
スクリプトは終了しますが trap
コマンドにしかけていたコマンドが実行されません。
これは、 ./test.sh
スクリプト内で trap
コマンドに仕掛けていたシグナル ( SIGINT )と kill
コマンドによって送信されたシグナル ( SIGTERM ) が一致しなかったためです。
$ ./test.sh
Terminated
「終了」を判定するにはいくつものシグナルをトラップしなければならないの?
人生の終わり方が様々 ( 大往生、事故、病気、… ) であるように、シェルスクリプトの終了の仕方も様々 ( SIGINT、SIGTERM、… ) です。
シグナルのうち、スクリプトを終了させるものを 終了シグナル と呼びます。
終了シグナル がいくつもあるということは、ひとくくりに 「終了」 と呼んでいるものをトラップするためには、 全ての 終了シグナル をトラップする必要があるのでしょうか?
終了シグナルの文だけ、 trap
コマンドを呼び出す処理を記述するのは大変です。
良い方法はないでしょうか?
EXIT シグナルをトラップする
すべての 終了シグナル 発生をトラップする良い方法があります。
終了シグナルが実行された後には EXIT というシグナルが送信されます。
少し変わったシグナルで、 終了シグナルが送信された後に 自分自身に対して送信するという特性があります。
- 別プロセスが SIGINT シグナルを送信
- SIGINT シグナルを受信
- 自プロセスに EXIT シグナルを送信
- EXIT シグナルを受信
先ほどのスクリプトを書き換えて、trap
するシグナル名を EXIT に変えてみます。
#!/usr/bin/env bash
trap 'echo "trap EXIT"' EXIT
cat
変更後、スクリプトを実行します。
別のターミナルで SIGINT シグナルと SIGTERM シグナルを送信してみます。
# スクリプト実行後に CTRL+C を押して、 SIGINT シグナルを送信
$ ./test.sh
^Ctrap EXIT
# スクリプト実行後に kill <PID> を実行して、SIGTERM シグナルを送信
$ ./test.sh
trap EXIT
Terminated
今度は SIGINT シグナル、 SIGTERM シグナルのいずれが送信された場合でも、 trap
の処理が実行されていることがわかります。
問答無用で強制停止させる SIGKILL シグナル
終了シグナルの中でも SIGKILL というシグナルはさらに変わった特性があります。
どうしようもなかった時に、プロセスを強制終了するためのシグナルになりますが、できるだけ使わない方がよいです。
というのも、このシグナルは EXIT トラップを使っても捕まえることができないためです。
プログラム内で想定されている後始末処理が実行されずにプロセスが頓死してしまうので、いろいろな弊害が起きる可能性があります。
例えば、データベースプロセスは正常に停止する場合、メモリ内に残っている情報をディスクにフラッシュしますが、 SIGKILL シグナルを受けて停止した場合にはメモリ内のデータが消えてしまいます。
不要なプロセスを止めたいと思ってもできるだけ SIGKILL プロセスは送信しないようにしましょう。
# EXITシグナルをトラップするスクリプトを実行
$ ./test.sh
# プロセスを一覧表示
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 11840 2896 pts/0 Ss 11:09 0:00 /bin/bash
root 18 0.0 0.0 11840 2888 pts/1 Ss 11:09 0:00 bash
root 113 0.0 0.0 4396 744 pts/0 T 12:47 0:00 cat
root 120 0.0 0.0 4396 760 pts/0 T 12:59 0:00 cat
root 122 0.0 0.0 11700 2620 pts/0 S+ 13:04 0:00 bash ./test.sh
root 123 0.0 0.0 4396 760 pts/0 S+ 13:04 0:00 cat
root 124 0.0 0.0 51744 3372 pts/1 R+ 13:04 0:00 ps aux
# killコマンドで 122プロセスに対して KILL シグナルを送信。 ( -KILL でKILLシグナルを送信できる )
$ kill -KILL 122
trap
により後処理が実行されません。
$ ./test.sh
Killed
ひとこと
ややこしいことを忘れて、とりあえず EXIT シグナルをトラップして後処理を仕掛けましょう。
ディスカッション
コメント一覧
まだ、コメントがありません