定期バッチの「サイレント死」を防ぐ。シェルスクリプトのTrapとWebhook通知の安全設計
目次
開閉
深夜や早朝に自動実行される定期バッチ処理。
ログのローテーション、データベースのバックアップ、外部SaaSとの定期データ同期など、現場では数多くのシェルスクリプトが人知れず動いています。
しかし、ある日突然「先週からバックアップファイルが1件も生成されていない」「集計スクリプトが途中でエラー終了していたのに、誰も気づいていなかった」という、いわゆる 「サイレント死」 に遭遇して背筋が凍った経験はないでしょうか。
cronは標準エラー出力(stderr)をメールで送信する設計になっていますが、現代の開発現場でサーバーのローカルメールを監視している人はほとんどいません。
今回は、どんなに小さな自動化スクリプトであっても確実にエラーをトラップし、問題発生時にSlackなどのチャットツールへ自動でアラートを飛ばすための「シェルスクリプト安全設計」を解説します。
安全なスクリプトの出発点:set -euo pipefail
まずはシェルスクリプトの冒頭(シバンの直後)に必ず記述すべき定型宣言です。
#!/usr/bin/env bash
set -euo pipefail
それぞれのオプションの意味を正しく把握しておきましょう。
-e(errexit): コマンドが1つでも非ゼロ(エラー)で終了した場合、直ちにスクリプトの実行を中断します。デフォルトのbashは途中でエラーが起きても構わず次の行を実行してしまうため、これがないと被害が拡大します。-u(nounset): 未定義の変数(宣言されていない変数)を参照しようとした際にエラーとしてスクリプトを停止します。たとえばrm -rf "$TARGET_DIR/*"で変数が空だった場合の惨事を防ぎます。-o pipefail: パイプライン(cmdA | cmdB)の途中でエラーが発生した場合に、全体の終了ステータスをエラーとして扱います(デフォルトでは最後のcmdBのステータスしか見ません)。
これら3つを指定するだけで、スクリプトの信頼性は劇的に向上します。
trap による確実なエラー捕捉と後処理
set -e によってエラー時にスクリプトが停止するようになっても、そのままでは「どの行で何が原因で死んだのか」が分かりません。また、作成した一時ファイルがディスクに残り続けてしまいます。
そこで登場するのが Bashの組み込みコマンド trap です。
#!/usr/bin/env bash
set -euo pipefail
# 一時作業ディレクトリの作成
WORK_DIR=$(mktemp -d /tmp/batch_work.XXXXXX)
# スクリプト終了時に必ず実行される後処理
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
# エラー発生時に呼び出されるハンドラー
handle_error() {
local exit_code=$?
local line_no=$1
echo "[ERROR] スクリプトが異常終了しました (行: $line_no, 終了コード: $exit_code)" >&2
send_slack_alert "$line_no" "$exit_code"
}
trap 'handle_error $LINENO' ERR
trap ... EXIT: 正常終了・異常終了・Ctrl+Cなど、スクリプトがどんな理由で終了した場合であっても確実にcleanup関数が実行され、一時ファイルが消去されます。trap ... ERR: コマンドがエラー(非ゼロ)を返した瞬間にhandle_errorが発火し、エラーが発生した行番号($LINENO)と終了ステータス($?)を捕捉します。
エラー時のWebhook自動通知の実装
エラーを検知したら、SlackのIncoming WebhookにJSONペイロードをPOSTしてチャンネルへ即時通知します。
curl を用いたシンプルな通知関数は以下のようになります。
WEBHOOK_URL="https://hooks.slack.com/services/xxx/yyy/zzz"
SCRIPT_NAME=$(basename "$0")
send_slack_alert() {
local line_no=$1
local exit_code=$2
local host_name
host_name=$(hostname)
local payload
payload=$(cat <<EOF
{
"text": "🚨 *定期バッチ異常終了アラート*",
"attachments": [
{
"color": "danger",
"fields": [
{ "title": "スクリプト名", "value": "${SCRIPT_NAME}", "short": true },
{ "title": "実行ホスト", "value": "${host_name}", "short": true },
{ "title": "発生行番号", "value": "L${line_no}", "short": true },
{ "title": "終了コード", "value": "${exit_code}", "short": true }
]
}
]
}
EOF
)
curl -s -X POST -H 'Content-type: application/json' \
--connect-timeout 5 --max-time 10 \
--data "$payload" "$WEBHOOK_URL" || true
}
外部通信のタイムアウト(--connect-timeout 5 --max-time 10)と || true を添えておくことで、万が一Slack側が不調な場合でも、通知処理自体がハングしてプロセスを占有し続けるのを防ぎます。
まとめ
「スクリプトが途中で失敗するかもしれない」という前提に立ち、set -euo pipefail で異常を早期検知し、trap で後処理とWebhook通知を担保する。この一連のテンプレートを持っておくだけで、日々のバッチ運用の安心感は格段に変わります。
シェルスクリプトを用いたデータベースバックアップの実務設計については、以下の記事でも具体的な実装パターンを解説しています。あわせて参考にしてみてください。