背景
最近、業務でのプログラミングにて、
シェルで書いた方が動くものが楽に書ける場合が多々ある。
ただシェルだとエラーハンドリング周りが若干面倒なので、
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.sh [INFO]Main [INFO]Finish
それでは次のように無理やり動かないコマンドをmain関数の中に入れてみる。
#!/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
#!/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"
main.shを実行すると以下のようになる。
$ ./main.sh [INFO]Main [INFO]Lib Main [INFO]Finish
test.shを実行すると以下のようになる。
[INFO]Setup testLibMain [INFO]Lib Main Ran 1 test. OK
まとめ
Bashでは「set」コマンドと「trap」コマンドを使うことで、
Try-Catch-Finallyの機構を作ることができ、
エラーハンドリング・リトライ処理に利用できて便利。
|
【楽天ブックスならいつでも送料無料】入門bash第3版 [ キャメロン・ニューハン ] 価格:3,024円(税込、送料込) | デスクの横に置いておいて、困ったときにパラパラめくる辞書のように使えるので割と便利。 |