背景・目的
GNU/Linux を使っていると、シェルスクリプトや各種コマンド、Python などのプログラムが「終了コード(exit status)」を返している場面に頻繁に出会う。終了コードは一見すると単純で、成功なら 0、失敗なら 0 以外という “雑な” 仕組みに見える。しかし実務では、cron・CI・デプロイ・監視・バッチ処理など「自動化された連鎖」の中で、終了コードは「次の処理に進むか」「止めるか」「通知するか」を決める最小単位の合図になる。ログが膨大で読めない時でも、終了コードだけは機械が確実に読める。そのため、終了コードの意味づけを曖昧なまま放置すると、運用の信頼性が落ちる。
本稿の目的は、終了コードについて「一般的な慣習」と「規約(仕様・標準・実装依存の予約領域)」を整理し、最終的にポリシーとして何を採用すべきかを一般論として明確化することである。ここでは、個別の経緯やローカルな事情は捨て、再利用できる形の論考としてまとめ直す。
終了コードとはそもそも何なのか
終了コードは、あるプログラム(プロセス)が終了したときに、親(通常はシェルや呼び出し元プロセス)へ返す「結果の数値」である。典型的には「成功か失敗か」を示すフラグとして使われる。POSIX の世界では「成功は 0、失敗は非 0」が基本であり、具体的にどの数値がどの意味かは、プログラム側が設計として決める余地が大きい。ただし、どこまでも自由というわけではない。シェルや OS、既存ツールが既に「予約的に扱う領域」を持っており、そこに勝手な意味を割り当てると衝突が起きる。
また、プロセス終了には大きく二種類ある。プログラムが自分で exit(n) する「通常終了」と、シグナルなどで落ちる「異常終了」である。親は wait/waitpid などで子の終了状態を受け取り、終了コードを解釈する。Linux の man ページでも、子がシグナルで終わったか、通常終了したかを判別するマクロ(WIFSIGNALED / WIFEXITED など)が説明されている。つまり「終了コード」と呼んでいるものも、内部的には “終了のしかた” とセットで扱われる。[1]
何をねらいとしているのか
終了コードが狙っている本質は「人間向けの説明」ではなく「機械向けの分岐条件」である。具体的には次の用途が中心になる。
- 制御フローの分岐:成功なら次へ、失敗なら中断・リトライ・通知など。
- パイプラインの健全性判定:複数の処理を連結しているとき、どこで落ちたかを機械的に検出する。
- 監視・運用のシグナル:ジョブ管理や CI は “exit code” を第一級の結果として扱う。
- 最小情報での失敗通知:ログが読めない状況でも「失敗した事実」だけは確実に伝える。
重要なのは、終了コードは「情報量が少ない」ことが前提の仕組みだという点である。終了コードだけで原因を完全に表現することは難しい。だからこそ「ログとセットで設計する」ことが一般解であり、終了コードの細分化は必要最小限に留めるべき、という方向に自然に収束する。
一般的な慣習としてはどうなのか
0 は成功、非 0 は失敗
これは最も広く共有される前提である。POSIX シェルの仕様でも、コマンドが失敗した場合は非 0 の終了ステータスを返す、という基本が示される。[2]
1 は “General failure” として多用される
実務で最も多いのは「失敗したら 1」である。Python の公式ドキュメントですら「Unix プログラムは一般に、コマンドラインの文法エラーを 2、その他のエラーを 1 にすることが多い」と述べており、さらに sys.exit(“message”) のように文字列を渡すと stderr に表示して終了コードが 1 になる、という “一般的失敗の短絡” が言語仕様として用意されている。[3]
一方で「1 は便利だが、情報量が少ない」ことも古くから指摘されている。つまり 1 を多用する現実はあるが、1 だけでデバッグできる設計にはならない。ここが「ログ設計」「エラー出力設計」と不可分になる。
126 / 127 は衝突しやすい予約領域
Bash の公式マニュアルには、コマンドが見つからない場合は 127、見つかったが実行できない場合は 126、という挙動が明記されている。[4] これらをアプリ固有の意味として再定義すると、「コマンドが無いのか」「自分のプログラムが 127 を返したのか」が区別できなくなる。ゆえに 126 / 127 は “予約扱い” として温存するのが実務的に安全である、という結論になりやすい。
128 以上はシグナル由来として扱われやすい
広く参照される解説では「128 + n はシグナル n による終了」といった慣習が紹介されており、Ctrl + C の SIGINT(2) なら 130 という解釈が一般化している。[5] 実際、ジョブ管理などでも 130 = SIGINT(130 – 128 = 2)という形で説明されている。[6] したがって 128 以上の領域も、アプリ独自コードとして自由に使うと “シグナル死” と混同されやすい。
小項目まとめ
| 観点 | 要点 | 実務上の扱い |
|---|---|---|
| 0 と非 0 | 成功は 0、失敗は非 0 が基本 | 最小ルールとして必ず尊重する |
| 1 の多用 | 一般的失敗として 1 が最も多い | 詳細はログに寄せ、1 をデフォルトにする |
| 126 / 127 | シェル側の予約的意味が強い | アプリ独自用途で再定義しない |
| 128 以上 | シグナル由来として解釈されやすい | アプリ独自の終了コードに使わない |
歴史的にはどうなのか
終了コードの文化は、UNIX の「小さなプログラムを組み合わせる」という思想と強く結びついている。UNIX は、標準入力・標準出力・標準エラーと合わせて「終了ステータス」を提供し、これを組み合わせの接着剤にした。つまり “成功なら次へ” を機械が判断できるようにして、複雑な処理をパイプやスクリプトで組めるようにした。その結果、終了コードは「人間が読む診断書」ではなく「機械が読む信号」になった。
この歴史的背景は、終了コードを過剰に細分化して「終了コードだけで説明しよう」とする方向が本質から外れることを示唆する。説明はメッセージで行い、終了コードは分岐のための最小限に留める、という設計は UNIX 的に自然である。
世の中にはどのような規約があるのか
ここでは「仕様としての規約」と「事実上の規約(慣習)」を分けて整理する。
1) POSIX / シェル仕様(枠組み)
POSIX のシェル仕様は、終了ステータスの枠組み(成功/失敗、実装が許容する範囲など)を定める。シェル実装がシグナル等で終了した場合に 255 を超える値を割り当てることを許容する、という注意も含まれる。[2]
2) Bash の仕様(126 / 127)
Bash は「見つからない = 127」「実行不可 = 126」を明確に規定する。これは運用上の衝突を避けるため、アプリ側で再定義しないのが安全、という強い根拠になる。[4]
3) シグナル終了(128 + n)という慣習
POSIX レベルでは「シグナルで終わったかどうか」を wait 系の仕組みで判別するが、シェルの世界では “人間が見やすい数値” として 128 + n が広く扱われる。一般向けの解説や実務文脈でも、この規則は共通語彙になっている。[5]
4) sysexits(64–78)という体系
sysexits は、もともと BSD 系で使われてきた「プログラムの終了コードに意味を持たせるための分類」を提供する。たとえば EX_USAGE(64) は使い方の誤り、EX_SOFTWARE(70) はソフトウェア内部エラーなど、意味が整理されている。Linux の man-pages でも sysexits.h として参照できる。[7] OpenBSD の man ページにも同様の説明がある。[8] 日本語の解説も存在し、EX_CONFIG(78) など “設定不備” を表すコードが定義されている。[9]
5) 「予約コードを避けよ」という啓蒙(解説・コミュニティ)
TLDP の Advanced Bash-Scripting Guide は、特定の範囲に特別な意味があるとして注意を促している。[10] また、Unix 系の Q&A でも “reserved codes” をアプリ固有に使うことの混乱が議論されている。[11]
この種の啓蒙は「規格」ではないが、運用上の事故を減らす実践知として価値がある。
小項目まとめ
| 枠組み | 性質 | 読み替えポイント |
|---|---|---|
| POSIX | 仕様(枠組み) | 成功/失敗の基本と解釈の前提を与える |
| Bash 126/127 | 実装仕様(事実上の標準) | 衝突を避けるため予約として温存する |
| 128 + n | 慣習 | シグナル終了と誤認されるため独自利用しない |
| sysexits 64–78 | 体系(意味付き分類) | 必要時のみ採用し、ヘッダーで明示する |
| 啓蒙記事・Q&A | 実践知 | 事故パターンを共有しており運用設計に役立つ |
それらをどう取り扱うべきなのか
ここまでの整理を踏まえると、現実的な取り扱いは次の二段構えになる。
第 1 段:最小集合を守る(衝突回避)
- 0 は成功。
- 1 は一般的失敗(General failure)として使う。
- 126 / 127 はシェル予約として温存する(アプリ独自で再定義しない)。[4]
- 128 以上はシグナル慣習と衝突しやすいので、アプリ独自用途では避ける。[5]
この段階だけでも、他ツールとの衝突が減り、運用時の誤解釈が大幅に減る。
第 2 段:本当に必要な場合だけ分類を導入する
終了コードの細分化は「便利そうに見える」が、運用とレビューを複雑化させやすい。そこで、分類が必要な場合だけ sysexits(64–78) を採用する。採用するなら、そのスクリプトのヘッダーで明示し、ログでも説明する。sysexits は意味体系が既に共有されているため、独自体系を自作するより衝突と誤解が少ない。[7]
つまり「基本は最小集合」「必要時だけ sysexits」という分離が、一般性と一貫性の両立に向く。
小項目まとめ
| 段階 | 方針 | 狙い |
|---|---|---|
| 第 1 段 | 0 / 1 を基本に、126 / 127 / 128+ を避ける | 外部の慣習・仕様と衝突しない |
| 第 2 段 | 必要なときだけ sysexits(64–78) を採用する | 意味のある分類を最小の追加コストで実現する |
最終的にポリシーを定めるにあたり、何を気にしたのか
ポリシー策定で重視した観点は次の通りである。
- 一般性:GNU/Linux の広い慣習と衝突しないこと(126/127/128+ の予約領域)。
- 一貫性:Shell/Python/Ruby で意味がズレないこと。言語ごとに exit code 定義を重複させると、更新漏れや矛盾が発生しやすい。
- 運用性:終了コードに情報を詰め込みすぎず、ログ設計を主役に据えること。終了コードの “体系” がポリシー全体を支配すると、実装規律(明示的エラーハンドリング、ログ、I/O 安全性、テスト、ドキュメント)がおろそかになる。
- 保守性:ポリシー変更を最小箇所で反映できる構造にすること(exit code を Shell セクションに集約し、他言語は参照)。
- 読解可能性:一般の人が読んでも理解できるよう、抽象語だけでなく「何を避けるか」「なぜ避けるか」を書くこと。
どのようなポリシーを定めたのか
最終的に、以前のポリシーを見直し、リポジトリの doc/POLICY は「Implementation Policies」として、Shell/Python/Ruby を同一ドキュメントに統合した。設計思想・ログ形式・終了コードの意味づけを言語横断で揃え、実装規律(POSIX 準拠、明示的エラーハンドリング、ドキュメント更新、テスト構造)を規定する。
ここで、終了コードについては次のように定めた。
- 0: Success
- 1: General failure(基本はここに寄せる)
- 126: exists but not executable(予約、再定義しない)[4]
- 127: not found(予約、再定義しない)[4]
- 128+ はシグナル慣習があるため、アプリ独自用途に使わない。[5]
- 細分類が必要な場合のみ sysexits(64–78) を採用し、ヘッダーで明示する。[7]
結論としてどのようなポリシーができあがったのか
結論は「終了コードは最小集合で衝突回避を優先し、説明はログで行う。必要時のみ sysexits を限定採用する」という形に落ち着いた。これにより、終了コード設計が過剰に肥大化せず、一般的慣習と整合しつつ、リポジトリ内で矛盾が生まれにくい。
ポリシーには終了コード以外も含め、何が書かれているのか
終了コードはポリシー全体の一部に過ぎない。実際の doc/POLICY では、より重要な実装規律が中心になっている。要点は次の通りである。
Shell Script(実装の基準点)
- POSIX 準拠(#!/bin/sh、bash 依存を避ける、local 不使用)。
- 関数化と責務分離(短く単一目的)。
- ヘッダー・ドキュメントの必須構造(Description / Requirements / Usage / Options / Test Cases / Version History の順序)。
- ログ形式統一([INFO] / [WARN] / [ERROR]、エラーは stderr)。
- エラーハンドリング(set -e を使わず明示的チェック、早期検証、exit は異常系のみ、通常は return で伝播)。
- I/O 安全性(破壊的操作の回避、パス検証、既存の一時ファイル規約尊重)。
- CLI 規律(-h は常に 0 で終了、引数不正は usage+失敗)。
- テストと運用(cron 前提、PATH 等の明示、プラグイン構造の戻り値伝播)。
Python / Ruby(Shell と同型の思想で実装する)
- 設計思想・ログ・終了コードは Shell と同一(exit code の再定義はしない)。
- それぞれの言語で、エントリポイント(main)・標準ライブラリ縛り・テスト配置・互換性制約を明確化し、Shell と同じ規律(明示的エラー処理、早期検証、ログ整形)に従わせる。
参考文献
- Linux man-pages: wait(2)(終了状態の判定マクロ WIFSIGNALED / WIFEXITED など) https://man7.org/linux/man-pages/man2/wait.2.html
- The Open Group Base Specifications Issue 8: Shell Command Language(終了ステータスに関する仕様を含む) https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html
- Python 公式ドキュメント sys.exit(文字列を渡すと stderr 出力+終了コード 1、Unix の慣習にも言及) https://docs.python.org/3/library/sys.html
- Bash Reference Manual: Exit Status(126/127 の意味) https://www.gnu.org/s/bash/manual/html_node/Exit-Status.html
- Qiita: Bash の予約された exit code と 128+n(シグナル由来の扱い) https://qiita.com/Linda_pp/items/1104d2d9a263b60e104b
- IBM Docs: LSF job exit codes(130=SIGINT などの説明) https://www.ibm.com/docs/ja/spectrum-lsf/10.1.0?topic=logging-lsf-job-exit-codes
- Linux man-pages: sysexits.h(3head)(64–78 の体系) https://man7.org/linux/man-pages/man3/sysexits.h.3head.html
- OpenBSD man pages: sysexits(3)(sysexits の体系) https://man.openbsd.org/sysexits.3
- manpages 日本語ミラー: sysexits(3) 解説(EX_CONFIG 等の説明) https://nxmnpg.lemoda.net/ja/3/sysexits
- TLDP Advanced Bash-Scripting Guide: Exit Codes With Special Meanings(予約領域への注意喚起) https://tldp.org/LDP/abs/html/exitcodes.html
- Unix & Linux Stack Exchange: reserved codes 議論(予約領域を独自用途で使うことの混乱) https://unix.stackexchange.com/questions/242111/using-reserved-codes-for-exit-status-of-shell-scripts