はじめに
kill
コマンドと trap
コマンドはシェルでシグナルの送信と受信を行うためのコマンドです。このコマンドは意外と適切ではない使われ方をよく見かけます。この記事では kill
と trap
の基礎知識を解説します。
POSIX準拠のkillコマンドの構文
POSIX で標準化されている kill コマンドの使い方は次のとおりです。POSIX で標準化されているというのは移植性が高い書き方ということを意味しています。シグナル名の指定には -s
オプションが必要です。そして signal_name
であって signal_number
でないことに注意してください。POSIX シェルの世界にシグナル番号という概念はありません。シグナル番号を指定した書き方は避ける方をおすすめします。
kill [-s signal_name] pid...
kill -l [exit_status]
例: kill 1234, kill -s TERM 1234, kill -l 141
POSIX では SIG 接頭詞を付けたシグナル名に対応することは要求されていません。dash ではエラーになります。
$ dash -c 'kill -s SIGTERM $$'
dash: 1: kill: invalid signal number or name: SIGTERM
$ dash -c 'kill -s TERM $$'
Terminated
次のような使い方はよく見かけます。一応どの環境でも動くはずです。POSIX で標準化されているように見えますがオプションの XSI 拡張となっているので実装されない環境があっても不思議ではありません。
kill [-signal_name] pid...
kill [-signal_number] pid...
例: kill -9 1234, kill -TERM 1234
この構文は一言で言うならば POSIX で標準化される前の古い書き方です。元々廃止予定の書き方だったものが POSIX.1-2001 での Single UNIX Specification の POSIX への統合で XSI 拡張として生き残ってしまったものです。XSI 拡張とは簡単に言えば System V 系 Unix 用の仕様で、BSD Unix との移植性を考慮していません。
XSI 拡張の構文は POSIX のオプションのガイドラインに準拠していないため POSIX では対応を要求していないのでしょう。いろんな書き方を覚える必要はありません。最善の書き方である最初の書き方を使用してください。癖がついてしまっている人を強制する意図はありませんが、これからの人に使わせる必要はありません。シンプル is ベストです。説明するならこのような使い方もできるという補足に留めておきましょう。
シグナル番号の使用に関する一つの例外は 0
です。0
はプロセスが生きているか(シグナルが送信可能か)を調べるための「シグナル名」として使うことができます。
$ kill -s 0 # 0 はシグナル番号のようだがシグナル名扱い
trapでシグナル番号やSIG接頭詞は使わない
まず避けるべき書き方を提示します。
trap 'echo int' 2 # シグナル番号は POSIX で必須となっていない
trap 'echo int' SIGINT # 頭に SIG を付けると動かないシェルがある
trap 'echo int' int # 小文字には対応していないシェルがある
理由はコメントに書いた通りですが、これらは POSIX で標準化されている書き方ではありません。POSIX としては SIGINT
が 2
であるという保証はしていません(XSI拡張を実装している環境を除く)。以下の表は XSI 拡張としての定義です。シグナル番号とシグナル名の対応は POSIX でこのように実装すべきと必須にしてないため、(おそらくこの通りになっているはずですが)もしかしたら違うかもしれません。
[XSI] XSI-conformant systems also allow numeric signal numbers for the conditions corresponding to the following signal names:
1 SIGHUP
2 SIGINT
3 SIGQUIT
6 SIGABRT
9 SIGKILL
14 SIGALRM
15 SIGTERM
[/XSI]
実際に動くか動かないかの話をすればおそらく動きます。しかし可読性が高いシグナル名を使えば良いのでわざわざシグナル番号を使う理由がありません。タイプ数をほんの数文字減らすよりも可読性の方が重要です。
シグナル名を使うと以下のシグナルも POSIX で標準化された範囲で使うことができます。
SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGFPE SIGHUP SIGILL
SIGINT SIGKILL SIGPIPE SIGQUIT SIGSEGV SIGSTOP SIGTERM SIGTSTP
SIGTTIN SIGTTOU SIGUSR1 SIGUSR2 SIGWINCH SIGURG
[XSI拡張]
SIGSYS SIGTRAP SIGVTALRM SIGXCPU SIGXFSZ
また trap
コマンドで冒頭に SIG
接頭詞を付けたり小文字で書くのは推奨しません。一部のシェルでエラーになります。
$ dash -c 'trap "echo int" SIGINT'
trap: SIGINT: bad trap
$ zsh -c 'trap "echo int" int'
zsh:trap:1: undefined signal: int
移植性なんか気にしないという考え方もあると思いますが、頭に SIG
を付けるのは単純に長くて入力が面倒な上に可読性の向上などのメリットはないので短く書きましょう。
kill -lでシグナル番号からシグナル名に変換
プロセスがシグナルで終了したときは「シグナル番号+128」の終了ステータスで終了します。
$ sleep 100 & # バックグランドで sleep コマンドを実行
[1] 2488442
$ kill -s INT 2488442 # sleep コマンドに SIGINT (シグナル番号 2) を送信
$ wait $! # sleep コマンドの終了ステータスを取得
[1]+ Interrupt sleep 100
$ echo $? # sleep コマンドの終了ステータス(シグナル番号2 + 128)
130
この 130
というシグナル番号をシェルスクリプトに記述してはいけません。なぜなら SIGINT のシグナル番号は 2
とは限らないからです。
この場合、kill -l
を使用すれば終了ステータスからシグナル名を取得することができます。この使い方は POSIX で標準化された使い方です。
$ kill -l 130 # 終了ステータスからシグナル名に変換可能
INT
$ kill -l 2 # シグナル番号からもシグナル名に変換可能
INT
一部の実装ではシグナル名からシグナル番号に逆変換をすることができますが、これは POSIX で標準化されておらず、実際に動作しないシェルがあります。
$ bash -c 'kill -l INT' # シグナル名からシグナル番号への変換はできるとは限らない
2
$ dash -c 'kill -l INT'
dash: 1: kill: Illegal number: INT
$ mksh -c 'kill -l INT'
E: mksh: kill: bad number: INT
$ yash -c 'kill -l INT'
INT
kill -l(引数なし)から対応表は作れない
kill -l
(引数なし)からシグナル番号とシグナル名の対応表を作ることはできません。これはシェルによってはシグナル番号が出力されないからです。
$ bash -c '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
16) SIGSTKFLT 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 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
$ bash --posix -c 'kill -l' # bash は POSIX モードではシグナル番号を出力しない
HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH IO PWR SYS RTMIN RTMIN+1 RTMIN+2 RTMIN+3 RTMIN+4 RTMIN+5 RTMIN+6 RTMIN+7 RTMIN+8 RTMIN+9 RTMIN+10 RTMIN+11 RTMIN+12 RTMIN+13 RTMIN+14 RTMIN+15 RTMAX-14 RTMAX-13 RTMAX-12 RTMAX-11 RTMAX-10 RTMAX-9 RTMAX-8 RTMAX-7 RTMAX-6 RTMAX-5 RTMAX-4 RTMAX-3 RTMAX-2 RTMAX-1 RTMAX
$ dash -c 'kill -l'
0
HUP
INT
QUIT
︙
RTMAX
$ ksh -c 'kill -l'
HUP
INT
QUIT
︙
RTMAX
$ zsh -c 'kill -l'
HUP INT QUIT ILL TRAP IOT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS
kill -l
の出力形式は POSIX で次のように標準化されています。
"%s%c", <signal_name>, <separator>
この時 separator
は改行またはスペースです。したがって bash も(POSIX 準拠モードでは)POSIX に準拠した出力ですが、シグナル番号は出力されません。kill -l
の出力形式はシェルによってかなり異なります。すべてのシェルに対応すれば対応表を作ることは不可能ではないかもしれませんが、終了ステータスは 0 から 255 までしか無いわけで、もし対応表が必要であれば、ループして kill -l 終了ステータス
から対応表を生成したほうが良いでしょう。もっとも必要になることはあまりないと思います。
trap -l は使わない(bash専用)
bash では kill -l
と同じ意味で trap -l
が使えますが、わざわざ使う必要はありません。POSIX でも標準化されていません。
さいごに
ということで、良いシェルスクリプトのための kill
と trap
の使い方でした。実践的な使い方としては説明することはまだまだあるのですが、長くなるのでまずはよく見かける、良くない使い方の訂正です。