背景
最近、業務でのプログラミングにて、シェルで書いた方が動くものが楽に書ける場合が多々ある。
ただシェルだとエラーハンドリング周りが若干面倒なので、
Try-Catch-Finallyできたらいいなと思って調べたら、
それができたのでメモしてみた。
環境
- OS
- Linux 2.6.32-279.el6.x86_64 #1 SMP Fri Jun 22 12:19:21 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux
内容
Try-Catch-Finally
では実際にTry-Catch-Finallyをbashで実現してみる。
これには「set」コマンドと「trap」コマンドを利用する。
それぞれ、こことここを参考にした。 実際のソースコードは以下。
#!/bin/bash function main() { trap catch ERR echo "[INFO]Main" return 0 } function catch() { echo "[ERROR]Fail" } function finally() { echo "[INFO]Finish" } # Entry Point set -eu trap finally EXIT main
「set -eu」と「trap finally EXIT」と「trap catch ERR」を設定することで、
コマンドがエラーするとcatch関数に、終了するとfinally関数に処理が移るようにした。
「trap catch ERR」は関数ごとに設定しないと動いてくれないので、
main関数に記述してある。 以下のソースコードを中身とするmain.shという名前のファイルを作成して実行すると次のような結果となる。
それでは次のように無理やり動かないコマンドをmain関数の中に入れてみる。$ ./main.sh [INFO]Main [INFO]Finish
#!/bin/bash function main() { trap catch ERR echo "[INFO]Main" diff test return 0 } function catch() { echo "[ERROR]Fail" } function finally() { echo "[INFO]Finish" } # Entry Point set -eu trap finally EXIT main
diffコマンドの引数が一つ足りないため、必ずエラーになる。
それでは、実行してみよう。
$ ./main.sh [INFO]Main diff: missing operand after `test' diff: Try `diff --help' for more information. [ERROR]Fail [INFO]Finish
catch関数の部分が実行されている。
Retry
Try-Catch-Finallyの形式にすることで、他のシステムとの連携タイミングや通信に一時的な問題が発生して
スクリプトが動かない場合、
簡単に全体のリトライ処理が書ける。
#!/bin/bash function main() { trap catch ERR echo "[INFO]Main" return 0 } function catch() { echo "[ERROR]Fail" echo "[INFO]Retry" main } function finally() { echo "[INFO]Finish" } # Entry Point set -eu trap catch ERR trap finally EXIT main
ただ、catch関数でmain関数を呼んだだけである。 先ほどと同じように、
必ず失敗するコマンドを入れて実行してみる。
#!/bin/bash function main() { trap catch ERR echo "[INFO]Main" diff test return 0 } function catch() { echo "[ERROR]Fail" echo "[INFO]Retry" main } function finally() { echo "[INFO]Finish" } # Entry Point set -eu trap catch ERR trap finally EXIT main
実行結果は以下。
$ ./main.sh [INFO]Main diff: missing operand after `test' diff: Try `diff --help' for more information. [ERROR]Fail [INFO]Retry [INFO]Main diff: missing operand after `test' diff: Try `diff --help' for more information. [INFO]Finish
ちゃんとリトライ処理が走っている。
他にも、アラートメールを飛ばしたりするのも良い。
unit test
この形でスクリプトを書くとロジックの分離がしやすい気がするので、自分は別ファイルでロジックを書いて、
shunit2で単体テストとカバレッジを測っている。 具体的には以下のように書いている。
main.sh
#!/bin/bash function main() { trap catch ERR echo "[INFO]Main" lib_main return 0 } function catch() { echo "[ERROR]Retry" main } function finally() { if [ ! $? -eq 0 ]; then TITLE="error" TO=abc@example.com FROM=abc@example.com echo -e "Please check log file." | mail -s "$TITLE" -r "$FROM" "$TO" echo "[ERROR]Alert mail is sent to $TO ." fi echo "[INFO]Finish" } # Entry Point SOURCE_PATH="$(cd $(dirname $0);pwd)" source $SOURCE_PATH/lib.sh set -eu trap finally EXIT main
lib.sh
#!/bin/bash function lib_main() { echo "[INFO]Lib Main" touch "test.log" return 0 }
test.sh
main.shを実行すると以下のようになる。#!/bin/bash function oneTimeSetUp() { echo "[INFO]Setup" source ./lib.sh } function testLibMain() { lib_main test -e test.log ${_ASSERT_EQUALS_} "TestFileExists" $? 0 } . "path/to/shunit2/shunit2"
test.shを実行すると以下のようになる。$ ./main.sh [INFO]Main [INFO]Lib Main [INFO]Finish
[INFO]Setup testLibMain [INFO]Lib Main Ran 1 test. OK
まとめ
Bashでは「set」コマンドと「trap」コマンドを使うことで、Try-Catch-Finallyの機構を作ることができ、
エラーハンドリング・リトライ処理に利用できて便利。
【楽天ブックスならいつでも送料無料】入門bash第3版 [ キャメロン・ニューハン ] 価格:3,024円(税込、送料込) | デスクの横に置いておいて、困ったときにパラパラめくる辞書のように使えるので割と便利。 |