Tech Tips

  1. プログラミング
  2. 4288 view

[Shell][Bash]BashでTry Catch Finally

Contents

背景

最近、業務でのプログラミングにて、
シェルで書いた方が動くものが楽に書ける場合が多々ある。
ただシェルだとエラーハンドリング周りが若干面倒なので、
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円(税込、送料込)
  デスクの横に置いておいて、困ったときにパラパラめくる辞書のように使えるので割と便利。

プログラミングの最近記事

  1. PlatformIO IDE for VSCode を使用して VSCode で Ardu…

  2. ROS Docker イメージで発生した GPG error の解消方法

  3. Streamlit で訪れた国を色づけした世界地図を作成できるアプリケーションを作成してみ…

  4. M5Stack Core2 for AWS – ESP32 IoT開発キットで…

  5. D3.js v7 で点・線・テキスト・ツールチップ・ズームを設定する方法

関連記事

PAGE TOP