終了コードをあらためて考察する

シェルスクリプト、Python、Ruby のプログラムは、処理を終えると呼び出し元へ数値を返す。cron、CI、監視、デプロイでは、その数値が次の処理へ進むか、停止するか、通知するかを決める。そのため、終了コードへ詳しい意味を割り当てれば、障害の原因も正確に伝えられるように思える。

自身が管理する scripts リポジトリでは、Shell Script の実装規律を定めたポリシーを、Python と Ruby にも展開してきた[1]。その過程では、終了コード 2 をネットワーク到達不能、3 をローカルの前提条件不足とし、各言語のポリシーへ同じ分類を書いていた[2]

2026年1月24日の改定では、この独自分類を廃止した。成功は 0、通常の失敗は原則として 1 にまとめ、シェルが意味を持たせている領域を独自用途に使わない。細分類が必要なプログラムだけが sysexits を明示的に採用する方針へ変更した[2]。この変更は情報を減らすためではない。終了コードが担う機械制御と、ログが担う原因説明を分離するためである。


終了コードは診断書ではない

プロセスの終了状態には、正常終了とシグナルによる終了が含まれる。Linux の wait と waitpid は、子プロセスが通常終了したかを WIFEXITED で、シグナルにより終了したかを WIFSIGNALED で判定する。通常終了の場合に限り、WEXITSTATUS で下位8ビットの終了コードを取り出せる[3]

シェルから見える終了コードは、この終了状態を条件分岐へ使いやすい数値にしたものである。POSIX シェルでは、コマンドの終了状態が後続のコマンド動作へ影響する。成功と失敗を判定し、条件実行やパイプラインの結果を決めることが第一の役割である[4]

終了コードだけで原因を説明しようとすると、数値と障害原因の対応表が必要になる。呼び出し側は、その表を知っていなければ分岐できない。新しい原因を追加したときには、実装、文書、テスト、監視設定を同時に更新しなければならない。数値を増やしても、呼び出し側が 0 か非 0 かしか見ていなければ、制御に使える情報は増えていない。

原因、対象、再実行の可否、復旧方法は標準エラー出力とログへ記録する。終了コードには、呼び出し側が実際に分岐するための差だけを残す。この分担にすると、数値の意味を増やさなくても診断情報を失わない。


シェルがすでに使っている値を避ける

アプリケーションが返せる数値を、すべて独自用途へ使えるわけではない。POSIX Issue 8 は、実行するコマンドが見つからない場合を 127、見つかったが実行可能なユーティリティではない場合を 126 と定めている。コマンドがシグナルで終了した場合、シェルは 128 より大きい値を割り当てる[4]

POSIX の利用上の注意では、128 自体もシェルがコマンドを読み取る際の回復不能なエラーに割り当てられている。したがって、厳密には 128 と 128 より大きい値の意味は同一ではない。一方、Bash は致命的なシグナル N による終了を 128 と N の和で表す。コマンド未検出の 127、実行不能の 126 も同じである[5]

scripts リポジトリのポリシーは、実装差を細かく利用するのではなく、126、127、128 以上をアプリケーション固有のエラーへ割り当てない。128以上をまとめて避ける表現は、すべてを同じ原因とみなす分類ではなく、シェルが特別に扱う領域との衝突を避けるための保守的な境界である。


独自コード 2 と 3 を廃止した理由

ネットワーク到達不能を 2、ローカルの前提条件不足を 3 とする分類は、一見すると分かりやすい。しかし、実際の障害はこの二つへ安定して分離できない。名前解決に失敗した場合はネットワーク障害とも設定不備とも解釈できる。認証情報が存在しない場合はローカル資源不足だが、利用者から見れば接続失敗である。外部コマンドが見つからない場合には、独自の 3 よりシェルの 127 と混同しない設計が優先される。

分類が曖昧なまま数値だけ固定すると、実装者ごとに判断が分かれる。同じ原因がプログラムによって 1、2、3 のいずれかになり、共通ポリシーが一貫性を作るどころか、分類の解釈差をリポジトリ全体へ広げる。

呼び出し階層をまたぐと、さらに意味が崩れる。補助プログラムが 2 を返しても、上位の処理が複数の失敗をまとめて 1 に変換することがある。反対に、数値をそのまま伝播すると、上位プログラムが下位プログラム固有の分類へ依存する。どちらを選んでも、2 と 3 を全言語共通の契約として維持する費用に見合う分岐がなければ意味がない。

2026 年 1 月 24 日の改定では、Shell Script、Python、Ruby に重複していた終了コード定義を統合し、2 と 3 の固定的な意味を削除した[2]。現在の master では、終了コードの定義は言語別の章ではなく Common Policy に置かれている。0 を成功、1 を既定の失敗とし、126、127、128 以上を独自用途に使わない原則は維持されている[6]


sysexits は分岐が必要な場合だけ使う

終了原因に応じて呼び出し側の動作を変える必要がある場合、すべてを 1 にまとめると情報が不足する。たとえば、一時的な障害なら後で再実行し、設定不備なら管理者の修正を待つ処理では、両者を区別する値が制御に使われる。

sysexits は、64 から 78 までの値に、コマンドライン使用法の誤り、入力データ不正、一時的障害、権限不足、設定不備などの名前を与える。Linux man-pages に収録されているが、標準は BSD であり、POSIX の共通終了コード体系ではない。同じ資料も、適切な値の選択はしばしば曖昧になると注意している[7]

採用条件は、原因を詳しく表現したいかではなく、呼び出し側が値ごとに異なる処理を行うかである。EX_TEMPFAIL を受けたら再試行し、EX_CONFIG を受けたら再試行せず設定エラーとして通知する、といった契約が存在する場合には意味がある。ログを読む人へ原因を伝えるだけなら、1 と具体的なエラーメッセージで足りる。

scripts リポジトリでは、sysexits を使う場合、その採用と各値の意味をプログラムのヘッダーへ明記する。共通ポリシーの既定値を増やさず、必要なプログラムだけが局所的な契約を追加する構成である。


言語をまたいで同じ契約にする

終了コードの統一は、数値の一覧を揃えるだけでは成立しない。関数内部の失敗が、最上位のプロセス終了まで失われずに伝わる構造が必要である。

Shell Script では、関数内の通常の制御に exit を使わず、return で状態を呼び出し元へ返す。最上位で最終結果を終了コードへ変換する。set -e による暗黙の終了へ依存せず、どの失敗を捕捉し、どの値を返すかを明示する。

Python では main が整数を返し、sys.exit(main()) でプロセスの終了コードにする。Ruby でも main の戻り値を最上位の exit へ渡す。この構造なら、通常の関数呼び出しとプロセス終了を分離したまま、Shell Script、Python、Ruby が同じ 0 と 1 の契約を共有できる[6]

ヘルプや版情報の表示は、利用者が要求した処理を正常に完了した結果なので 0 とする。引数不正時の扱いは、usage を表示したという事実ではなく、コマンドとして要求を受理できたかで決める。現在のポリシーは、ツールごとの設計に応じて 0 または 1 を認める一方、動作を一貫させて文書化することを要求している[6]


結論

終了コードの設計で優先すべきなのは、原因の種類をできるだけ多く数値化することではない。呼び出し側が必要とする分岐を、他のシェルやツールと衝突しない形で安定して提供することである。

scripts リポジトリでは、成功を 0、通常の失敗を原則として 1 にまとめた。126、127、128 以上はシェルの意味と衝突するため独自用途に使わない。原因と対象はログへ記録し、値による分岐が実在する場合だけ sysexits を明示的に採用する。

この方針によって失われたのは、利用されていなかった分類の見かけ上の細かさである。得られたのは、Shell Script、Python、Ruby の間で崩れにくく、呼び出し階層をまたいでも意味を説明できる終了契約である。


参考文献

  1. id774, スクリプトの実装ポリシーと Emacs Lisp のガイドラインを策定した(2025-11-05). https://blog.id774.net/entry/2025/11/05/3028/
  2. id774, 3319 Unify implementation policies across Shell, Python, and Ruby with standardized exit code(2026-01-24). https://github.com/id774/scripts/commit/3466edb83e6be8289178ba837b02a505a3b25d39
  3. Linux man-pages project, “wait(2),” Linux man-pages, 6.18, 2026. https://man7.org/linux/man-pages/man2/wait.2.html
  4. The Open Group, “Shell Command Language,” The Open Group Base Specifications, Issue 8, 2024. https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html
  5. Free Software Foundation, “Exit Status,” GNU Bash Reference Manual, 2025. https://www.gnu.org/s/bash/manual/html_node/Exit-Status.html
  6. id774, “Implementation Policies,” scripts, 2026. https://github.com/id774/scripts/blob/master/doc/POLICY
  7. Linux man-pages project, “sysexits.h(3head),” Linux man-pages, 6.18, 2025. https://man7.org/linux/man-pages/man3/sysexits.h.3head.html