拡張 POSIX シェルスクリプト Advent Calendar 2013、5日目の記事です。 書いているうちにネタが変わってしまいました。
trap
というシェル組込みコマンドをご存じですか?
シグナルを受信したときに実行する処理 (シグナルハンドラー) を設定するものです。
例えば SIGINT
シグナル※を受信するとデフォルトではスクリプトが終了しますが、
trap
で捕まえてやると終了せずに継続することも可能です。
(※割り込み (INTerrupt)。 端末から実行したシェルスクリプトなら Ctrl+c で送信可)
$ sh -c 'trap "echo Boo!" INT; while :; do read i && echo "$i"; done'
…Ctrl+cを押しまくる…
^CBoo!
^CBoo!
^CBoo!
…Ctrl+zを押す…
zsh: suspended sh -c 'trap "echo Boo!" INT; while :; do :; done'
$ jobs
[1] + suspended sh -c 'trap "echo Boo!" INT; while :; do :; done'
$ kill %1
[1] + terminated sh -c 'trap "echo Boo!" INT; while :; do :; done'
この例では Ctrl+c では終わらせることができないので、 Ctrl+z でサスペンドしてから殺すなどしてください。
ところで手元の ksh 93u+ (2012-08-01) で試すとサスペンドできないんですが、
なんでだろう。read
中が駄目っぽい。どうしてなのか誰か調べて教えてください!!
$ ksh -c 'trap "echo Boo!" INT; while :; do read i && echo "$i"; done'
…Ctrl+cを押しまくる…
Boo!
Boo!
Boo!
…Ctrl+zを押してもサスペンドせず無視される…
ksh -c read
の実行をサスペンドできるか各種 ksh で軽く調べたところ、
以下は大丈夫。
- Solaris 10 (ksh 88 相当)
- AIX 7.1 (ksh 93 相当)
- Debian 6 (ksh 93s+ 2008-01-31)
- Ubuntu 10.04 (ksh 93t+ 2009-05-01)
以下は駄目。新し目の AT&T ksh に問題があるっぽい。
- Solaris 11 (ksh 93u 2011-02-08)
- Debian sid (ksh 93u+ 2012-08-01)
- CentOS 6 (ksh 93t+ 2010-06-21)
AT&T 由来の ksh は、このところ積極的に保守されているようだし、 もし気が向いたら調査してバグ報告してみたいところ。
閑話休題。
trap
は、シグナル受信だけでなく、
いくつかのイベント発生時のハンドラーを設定することもできます。
以下のようなものです。
DEBUG
- コマンドの実行ごと (外部コマンドだけでなく、関数、組込みコマンドも含む)
EXIT
- スクリプトの終了時
ERR
- 実行したコマンドの終了時、その終了コードが 0 でない場合
RETURN
(bash のみ)- 関数や
.
、source
で実行したスクリプトから戻った時
- 関数や
ああ、ようやく本題に入れます。今回紹介したいのはこの中の EXIT
です。
これを trap
でひっかけることで、スクリプトの終了時に任意の処理を実行できます。
C 言語を知っている人であれば atexit
(3) 相当と思っていただけるとわかるかと。
$ sh -c 'trap "echo Bye!" EXIT; echo "Hello!"'
Hello!
Bye!
簡単ですね。
SIGINT
や SIGTERM
シグナルを受けたときどうなるでしょうか。
$ sh -c 'trap "echo Bye!" EXIT; kill -TERM $$; echo "Huh?"'
Bye!
zsh: terminated sh -c 'trap "echo Bye!" EXIT; kill -TERM $$; echo "Huh?"'
$ bash -c 'trap "echo Bye!" EXIT; kill -TERM $$; echo "Huh?"'
Bye!
zsh: terminated bash -c 'trap "echo Bye!" EXIT; kill -TERM $$; echo "Huh?"'
$ ksh -c 'trap "echo Bye!" EXIT; kill -TERM $$; echo "Huh?"'
Bye!
zsh: terminated ksh -c 'trap "echo Bye!" EXIT; kill -TERM $$; echo "Huh?"'
$ zsh -c 'trap "echo Bye!" EXIT; kill -TERM $$; echo "Huh?"'
zsh: terminated zsh -c 'trap "echo Bye!" EXIT; kill -TERM $$; echo "Huh?"'
※ zsh: terminated 〜
は対話シェルに利用している zsh が出力しているメッセージ
でました、zsh の非互換! zsh はシグナルハンドラー※内で終了すると、
EXIT
ハンドラーを実行してくれません。酷い。
(※この例では SIGINT
のデフォルトのシグナルハンドラー)
ほかの sh と同じ動作を実現できないか、もう少し工夫してみます。
$ zsh -c 'trap "echo Bye!" EXIT; trap "exit -1" TERM; kill -TERM $$; echo "Huh?"'
zsh: exit 1 zsh -c 'trap "echo Bye!" EXIT; trap "exit 1" TERM; kill -TERM $$; echo "Huh?"
駄目でした。
※ zsh: exit 1 〜
は対話シェルに利用している zsh が出力しているメッセージ
では、これでどうだ!
$ zsh -c 'atexit(){ echo "Bye!"; }; trap atexit EXIT TERM; kill -TERM $$'
Bye!
Bye!
!? (AA略) 意味がわかりません。バグっぽい臭いがプンプンします。 もう嫌になってきました。
$ zsh -c 'atexit(){ echo "Bye!"; }; trap atexit EXIT TERM; kill -TERM $$'
Bye!
それっぽい動きになりました。しかし、よーく考えるとわかるのですが、 これだとどのイベントを契機に終了したのかスクリプトの終了コードで判別できなくなってしまいます。
$ zsh -c 'atexit(){ echo "Bye!"; }; trap atexit EXIT; trap "atexit; exit -1" TERM; kill -TERM $$'
Bye!
zsh: exit 255 zsh -c
ようやくほかの sh に近い動きになりました。
このスクリプトが bash でどうなるか試してみましょう。
$ bash -c 'atexit(){ echo "Bye!"; }; trap atexit EXIT; trap "atexit; exit -1" TERM; kill -TERM $$'
Bye!
Bye!
zsh: exit 255 bash -c
アハハハ八八八ノ ヽノ ヽノ ヽ (AA略) まだだ! まだ終わらんよ!!
$ bash -c 'atexit(){ echo "Bye!"; }; trap atexit EXIT; trap "trap - EXIT; atexit; exit -1" TERM; kill -TERM $$'
Bye!
zsh: exit 255 bash -c
$ zsh -c 'atexit(){ echo "Bye!"; }; trap atexit EXIT; trap "trap - EXIT; atexit; exit -1" TERM; kill -TERM $$'
Bye!
zsh: exit 255 zsh -c
ふぅ…。
こんな感じで bash, ksh, zsh には微妙な動作の違いがあったりするので、 ちょっと変わったことしようとするときは特に注意しましょう。
ところで、12月25日はクリスマスな上に、
OSS 界隈で地味に活躍されているふみやすさんの誕生日ですね!
っ http://www.amazon.co.jp/registry/wishlist/27M7TV8CEEF6G?sort=priority
逆に、あなたの書いた OSS や Blog や Advent Calendar が気に入ったら何か送りたく なってしまうかもしれないので、プロフィールや Web サイトに あなたの Amazon 欲しいものリストの URL を貼っておいてくださいね!
私が勤める OSSTech っていう某弊社で社員募集しているようです。 人材紹介会社を介さなければ、入社後に 20万円のボーナス! 「ふみやすっていう人に紹介された」と言ってもらえると私にもボーナス!! → https://www.osstech.co.jp/recruit/
よろしければ、これまで参加した/参加予定のほかの Advent Calendar もどうぞ。
- Ansible Advent Calendar 2023
- シェル芸 Advent Calendar 2023
- 闇の魔術に対する防衛術 Advent Calendar 2023
- Ansible Advent Calendar 2023
- Ansible Advent Calendar 2020
- DNS温泉 Advent Calendar 2019
- OSSTech Advent Calendar 2019
- Ansible Advent Calendar 2018
- OSSTech Advent Calendar 2018
- Debian/Ubuntu Advent Calendar 2017
- Linux Advent Calendar 2017
- Shell Script Advent Calendar 2017
- Shell Script Advent Calendar 2016
- OpenLDAP と仲間たち Advent Calendar 2015
- Postfix Advent Calendar 2014
- 拡張 POSIX シェルスクリプト Advent Calendar 2013