Bashプログラミング
 Computer >> コンピューター >  >> プログラミング >> Bashプログラミング

Bashスクリプトでtrapコマンドを使ってシグナルを捕捉する方法

シェルスクリプトが開始するタイミングを検出するのは簡単ですが、いつ終了するのかを正確に把握するのは必ずしも容易ではありません。スクリプトは作者の意図どおりに正常終了することもあれば、予期しない致命的なエラーによって途中で失敗することもあります。失敗時に処理途中の状態を残しておくと後で役立つ場合もあれば、逆に邪魔になってしまう場合もあります。いずれにせよ、スクリプトの終了を確実に検出し、あらかじめ用意しておいた方法で対応するためにこそ、Bashのtrapディレクティブが存在するのです。

障害発生時に適切に対応する

まず、スクリプト内の一つの失敗が、その後の失敗を連鎖的に引き起こす例を見てみましょう。/tmpに一時ディレクトリを作成し、そこへアーカイブを展開してファイルを処理した後、別の形式でまとめ直すプログラムを書いたとします。

#!/usr/bin/env bash
CWD=`pwd`
TMP=${TMP:-/tmp/tmpdir}

## 一時ディレクトリを作成
mkdir "${TMP}"

## ファイルを一時ディレクトリへ展開
tar xf "${1}" --directory "${TMP}"

## 一時ディレクトリへ移動してコマンドを実行
pushd "${TMP}"
for IMG in *.jpg; do
  mogrify -verbose -flip -flop "${IMG}"
done
tar --create --file "${1%.*}".tar *.jpg

## 元のディレクトリへ戻る
popd

## bzip2で圧縮
bzip2 --compress "${TMP}"/"${1%.*}".tar \
      --stdout > "${1%.*}".tbz

## クリーンアップ
/usr/bin/rm -r /tmp/tmpdir

通常このスクリプトは期待どおりに動作します。しかし、JPEGではなくPNGファイルばかり入ったアーカイブに対して誤って実行すると、処理の途中で失敗してしまいます。一つの失敗が次の失敗を呼び、最終的にスクリプトは、末尾にある一時ディレクトリ削除のコマンドまでたどり着かないまま終了してしまいます。

手動でディレクトリを削除すればすぐに復旧できますが、人がその場にいない場合はどうでしょう。次回スクリプトを実行するときには、中身の予測できない残骸ファイルだらけの一時ディレクトリが既に存在していることになり、スクリプトはそれに対処しなければなりません。

これに対抗する一つの方法として、スクリプトの冒頭に予防的な削除処理を追加し、ロジックを二重化するやり方があります。それも有効ではありますが、構造的な設計ではなく力技に頼る方法です。よりエレガントな解決策こそがtrapなのです。

trapでシグナルを捕捉する

trapキーワードは、実行中に発生しうるシグナルを捕捉します。killkillallコマンドを使ったことがあれば、デフォルトで送信されるSIGTERMというシグナルを、すでに使った経験があることになります。シェルが応答するシグナルは他にも数多く存在し、trap -l(listの略)でその大半を確認できます。

$ trap --list
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

これらのシグナルはすべてtrapで捕捉可能です。さらに、trapは以下の特殊な条件も認識します。

  • EXIT: シェルプロセス自体が終了するときに発生
  • ERR: 外部コマンド(tarmkdirなど)やビルトインコマンド(pushdcdなど)がゼロ以外のステータスで終了したときに発生
  • DEBUG: デバッグモードを表すブール値

trapの基本的な書き方

Bashでトラップを設定するには、trapに続けて実行したいコマンドのリストを記述し、その後にトリガーとなるシグナルのリストを指定します。

たとえば次のトラップは、プロセスの実行中にユーザーがCtrl+Cを押したときに送られるSIGINTを検出します。

trap "{ echo 'Terminated with Ctrl+C'; }" SIGINT

サンプルスクリプトへの適用

先ほどの一時ディレクトリ問題を抱えたサンプルスクリプトは、SIGINT・エラー・正常終了を検出するトラップを追加することで修正できます。

#!/usr/bin/env bash
CWD=`pwd`
TMP=${TMP:-/tmp/tmpdir}

trap \
"{ /usr/bin/rm -r "${TMP}" ; exit 255; }" \
SIGINT SIGTERM ERR EXIT

## 一時ディレクトリを作成
mkdir "${TMP}"
tar xf "${1}" --directory "${TMP}"

## 一時ディレクトリへ移動してコマンドを実行
pushd "${TMP}"
for IMG in *.jpg; do
mogrify -verbose -flip -flop "${IMG}"
done
tar --create --file "${1%.*}".tar *.jpg

## 元のディレクトリへ戻る
popd

## tarを圧縮
bzip2 --compress $TMP/"${1%.*}".tar \
--stdout > "${1%.*}".tbz

このようにしておけば、スクリプトがどのような経緯で終了しても、一時ディレクトリは確実に削除されます。なお、複雑な処理を行いたい場合は、Bashの関数を定義してtrap文から呼び出すことで、記述をすっきりと簡潔にできます。

Bashトラップを活用しよう

トラップは、スクリプトが成功裏に完了した場合でもエラーで中断された場合でも、常にクリーンな状態で終了させるために非常に有効です。自動的なガベージコレクションに完全に依存するのは決して安全ではないため、トラップの活用は普段から身につけておきたい良い習慣です。ぜひ自分のスクリプトに組み込んでみて、その効果を体感してみてください。

  1. Bash-it – スクリプトとエイリアスを一元管理できるBashフレームワーク

    Bash-itは、Bash 3.2+向けに設計されたコミュニティ製のBashコマンド&スクリプト集です。自動補完、テーマ、エイリアス、カスタム関数などが最初から含まれており、日々の作業でシェルスクリプトや独自コマンドを開発・管理・活用するための実用的なフレームワークを提供します。 Bashシェルを日常的に使っていて、散らかりがちなスクリプト、エイリアス、関数を簡単に一元管理したいなら、Bash-itは最適な選択肢です。~/binディレクトリや.bashrcファイルを汚すことなく、Bash-itをフォーク/クローンして、快適なシェル環境づくりを始めましょう。 LinuxへのBash-itのインス

  2. PCでレジストリクリーナーを使うメリットとは?効果と選び方を徹底解説

    レジストリクリーナー 本当にレジストリクリーナーは必要なのか?——これは当サイトに最も多く寄せられる質問の一つで、人によって答えが大きく分かれるテーマです。長年にわたってこうしたツールを使用してきた経験をもとに、レジストリクリーナーがPCにもたらす実際の効果について、本記事で詳しく解説します。 レジストリクリーナーとは? レジストリクリーナーは、Windowsの「レジストリ」と呼ばれる領域をスキャンするソフトウェアです。レジストリとは、PCの設定やオプション、各種情報を保存する大規模なデータベースで、いわばコンピュータの「記憶装置」のようなもの。最新のメール履歴からデスクトップの壁紙設定まで