定期バッチの「サイレント死」を防ぐ。シェルスクリプトのTrapとWebhook通知の安全設計

定期バッチの「サイレント死」を防ぐ。シェルスクリプトのTrapとWebhook通知の安全設計

2026/07/24
目次
開閉
バッチの異常終了に気づく様子

深夜や早朝に自動実行される定期バッチ処理。

ログのローテーション、データベースのバックアップ、外部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)と終了ステータス($?)を捕捉します。
Lowom 編集長 編集長
CAUTION trap内のコマンド失敗に注意
「`set -e` が有効な状態で `handle_error` 内のコマンドが失敗すると、ハンドラー自体が中断されてしまいます。通知関数の中では一時的に `set +e` にするか、失敗しても無視できるように `|| true` を付与しておくのが安全です」

エラー時の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通知を担保する。この一連のテンプレートを持っておくだけで、日々のバッチ運用の安心感は格段に変わります。

シェルスクリプトを用いたデータベースバックアップの実務設計については、以下の記事でも具体的な実装パターンを解説しています。あわせて参考にしてみてください。

Lowom 編集長
この記事を書いた人:Lowom 編集長 現役Webエンジニア

都内IT企業に勤める業界20年のWebエンジニア。業務効率化・自動化スクリプトや快適な開発環境の構築、厳選したツール・ガジェットの活用法など、手元で実際に検証したリアルな一次情報をお届けします。

運営者プロフィール詳細を見る