exit code(終了コード)は、シェルスクリプトが呼び出し元に伝える唯一のシグナルだ。cronはこの値を見てメールを飛ばすか判断し、CI/CDパイプラインはここで次のステップに進むかを決める。ここを正しく設計しておかないと、エラーを握り潰したまま処理が続いてしまう。
この記事では、シェルスクリプトのexit codeを体系的に設計する方法を解説する。Linuxの慣例コード、関数内での正しい伝播パターン、cronやCI/CDとの連携設計まで、RHEL 9 / Rocky Linux 9 / Ubuntu 24.04 LTSで動作確認済みのコード例を交えて紹介する。
この記事のポイント
・exit 0が成功、1が汎用エラー、2が引数誤りという慣例を守ると呼び出し元で正確に分岐できる
・126・127・128+nはシェル予約コードなので、スクリプト独自の番号と重複させてはいけない
・関数内ではreturnで返し、呼び出し側が$?で受け取る設計が安全な伝播パターン
・local宣言と代入を同じ行に書くとexit codeが消える落とし穴があり、必ず分離する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
exit codeとは何か、なぜ重要なのか
シェルスクリプトが終了するとき、必ず0~255の整数値を返す。これが exit code だ。0は成功、1以上は何らかの失敗を意味する。実行後すぐに
$? を参照すると、直前のコマンドまたはスクリプトの終了コードを取得できる。# スクリプトを実行してexit codeを確認する ./backup.sh echo "終了コード: $?" # 出力例(成功時) # 終了コード: 0 # 出力例(失敗時) # 終了コード: 1
Linuxが予約しているexit codeと慣例
スクリプト独自のexit codeを決める前に、シェルが予約している番号を把握しておく必要がある。これらと重複すると、呼び出し元が誤判断する。1. シェルが予約するexit code
| exit code | 意味 | 発生例 |
|---|---|---|
| 0 | 成功 | 正常終了 |
| 1 | 汎用エラー | 処理失敗全般 |
| 2 | 使い方の誤り | 引数が足りない・不正なオプション |
| 126 | コマンドを実行できない | 実行権限がない(chmod +x 未設定など) |
| 127 | コマンドが見つからない | PATHに存在しないコマンドを呼び出した |
| 128+n | シグナルnで強制終了 | 128+2=130(Ctrl+C)、128+9=137(SIGKILL) |
2. カスタムexit codeの設計方針
スクリプト独自のexit codeは 3~125 の範囲で定義するのが慣例だ。番号の意味はスクリプト冒頭に定数として定義しておくと、コードを読んだ人が迷わない。呼び出し元が if 文で分岐する際も番号を探す手間がなくなる。#!/bin/bash # ======================================== # exit code 定義(3~125 の範囲を使う) # ======================================== readonly E_SUCCESS=0 # 正常終了 readonly E_GENERAL=1 # 汎用エラー readonly E_USAGE=2 # 引数・使い方の誤り readonly E_FILE_NOT_FOUND=3 # 必要なファイルが存在しない readonly E_PERMISSION=4 # 権限エラー readonly E_CONNECTION=5 # 接続エラー(DB・API・外部サービス) readonly E_TIMEOUT=6 # タイムアウト # 引数チェック(足りなければ usage エラーで終了) if [[ $# -lt 2 ]]; then echo "Usage: $0
" >&2 exit ${E_USAGE} fi
関数内でexit codeを正しく伝播させる設計
スクリプトが大規模になると、複数の関数を組み合わせる構成になる。このとき exit code を正しく伝播させないと、呼び出し元が失敗を検知できなくなる。1. returnとexitの使い分け
関数内ではexit ではなく return を使う。exit を関数の中で呼ぶと、スクリプト全体が即終了してしまい、後片付け処理(trapのEXITハンドラ等)が意図通りに動かなくなる場合がある。#!/bin/bash readonly E_SUCCESS=0 readonly E_CONNECTION=5 # DB接続チェック関数 check_db_connection() { local host="$1" if ! ping -c 1 -W 2 "${host}" &>/dev/null; then echo "[ERROR] DBホスト ${host} に到達できません" >&2 return ${E_CONNECTION} # exit ではなく return で返す fi echo "[INFO] DB接続確認OK: ${host}" return ${E_SUCCESS} } # 呼び出し元で $? を受け取る check_db_connection "db.example.com" if [[ $? -ne 0 ]]; then echo "[FATAL] DB接続失敗。処理を中断します。" >&2 exit ${E_CONNECTION} fi echo "以降の処理を継続..."
2. サブシェルでexit codeが消えるlocal宣言の罠
コマンド置換($(...))を local 宣言と同じ行に書くと、exit code が正しく取得できなくなる。local コマンド自体が成功するため、$? は常に 0 になってしまうからだ。#!/bin/bash # NG: local 宣言と代入を同時に書くと $? が local の終了コード(0)になる bad_example() { local result=$(false) # false は exit 1 を返すが... echo "終了コード: $?" # 常に 0 が出力される(local が成功しているため) } # OK: 宣言と代入を分離する good_example() { local result result=$(false) # false は exit 1 を返す local code=$? echo "終了コード: ${code}" # 正しく 1 が出力される return ${code} } bad_example good_example
exit codeの設計を含むシェルスクリプトの設計力を体系的に身につけたい方は、シェルスクリプト実践講座(Linux Master Pro)もご覧ください。現場で使える設計パターンを体系的に学べます。
呼び出し元でexit codeを活用する設計
スクリプトが返した exit code を、上位のシステムが正しく受け取れるよう設計することも重要だ。1. cronでexit codeを活用するパターン
cronはデフォルトで exit code が 0 以外のジョブが発生したとき、MAILTO に設定したアドレスへメールを送る。これを活かして、エラー時だけ通知が届くようにできる。# /etc/cron.d/daily-backup MAILTO=admin@example.com PATH=/usr/local/bin:/usr/bin:/bin # スクリプトが exit 0 以外を返した場合にメールが届く 0 3 * * * root /opt/scripts/daily-backup.sh >> /var/log/daily-backup.log 2>&1
2. 上位スクリプトから呼び出すパターン
複数のスクリプトをオーケストレーションする親スクリプトでは、各子スクリプトの exit code を確認して処理を制御する。|| exit $? のパターンを使うと、子スクリプトの exit code をそのまま親スクリプトの exit code として返せる。#!/bin/bash # 親スクリプト: 複数の処理を順番に実行する run_step() { local step_name="$1" local script="$2" echo "[INFO] ${step_name} 開始..." "${script}" local code=$? if [[ ${code} -ne 0 ]]; then echo "[ERROR] ${step_name} が失敗しました(exit code: ${code})" >&2 return ${code} fi echo "[INFO] ${step_name} 完了" return 0 } # || exit $? でexit codeをそのまま親に返す run_step "DBバックアップ" /opt/scripts/db-backup.sh || exit $? run_step "ファイル同期" /opt/scripts/file-sync.sh || exit $? run_step "レポート生成" /opt/scripts/report.sh || exit $? echo "[INFO] 全ステップ正常完了" exit 0
3. CI/CDパイプラインとの連携
GitHub ActionsやJenkinsなどのCI/CDでは、スクリプトの exit code がジョブの成否を決める。意味のある値を返すことで、どのステップで何が失敗したかをログから即座に特定できる。# GitHub Actions の step 例 steps: - name: デプロイスクリプト実行 run: ./deploy.sh # exit code 3(ファイル未存在)が返ると steps.*.outcome が failure になる - name: デプロイ失敗時のロールバック if: failure() # 前ステップが失敗したときだけ実行 run: ./rollback.sh
よくある落とし穴とエラー対処法
1. set -e 環境でのexit codeの扱い
set -e(エラー時即終了)を使っているスクリプトでは、コマンドが 0 以外を返した瞬間にスクリプト全体が終了する。ただし、if 文や || の中では set -e が無効になることを知らないと混乱する。#!/bin/bash set -e # if 文の中では set -e が効かない(安全) if ! some_command; then echo "失敗しました" >&2 exit 1 fi # || の右辺も set -e の対象外(安全) some_command || { echo "失敗しました" >&2; exit 1; } # 関数をそのまま呼ぶ場合は set -e が有効(失敗で即終了) some_command # 失敗すると次の行に進まずスクリプトが終了する
2. $?は直後に変数へ退避する
$? は最後に実行したコマンドの終了コードしか保持しない。別のコマンドを実行した瞬間に上書きされるため、必要ならすぐ変数に退避すること。some_command exit_code=$? # 直後に変数へ退避する echo "処理を記録" # この時点で $? は 0(echo の終了コード)に上書きされる if [[ ${exit_code} -ne 0 ]]; then exit ${exit_code} fi
まとめ
| 設計項目 | 推奨パターン |
|---|---|
| exit code 定義 | スクリプト冒頭で readonly E_XX=数値 として定数化(3~125の範囲) |
| 関数内の返し方 | exit ではなく return で返す |
| local 宣言と代入 | local result を先に書き、次の行で result=$(コマンド) と分離する |
| $? の取得タイミング | 対象コマンドの直後に exit_code=$? で変数に退避する |
| 上位スクリプトへの伝播 | cmd || exit $? パターンで exit code をそのまま返す |
| cron との連携 | MAILTO を設定し、0 以外の exit code でメール通知を受け取る |
シェルスクリプト講座を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:シェルスクリプトのヒアドキュメント設計|設定ファイル・SQLクエリ・通知メール本文を動的生成する実装パターン
- この記事の属するカテゴリ:シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます