こんにちは、おうどんです🍜
夜間バッチが朝に止まっていた。 ログを見る。
エラーは、出ていない。
先輩「終了コードは見た?」
おうどん「……しゅうりょうこーど?」
先輩「プログラムが最後に返す数字。0 なら成功」
おうどん「0点なのに成功なんですか?」
……テストなら赤点です。 でも、プログラムの世界では 0 が満点なんです。
(会話は説明用の架空の場面です)
終了コードは、robocopy の「1 なのに成功」や、bash の pipefail の話でも、当たり前のように出てきた言葉です。 今回はその土台、終了コードそのものを基礎から見ていきます。
おうどんのイメージはこうです。
- プログラム=出前係さん
- 呼び出した側(シェルやジョブ)=店長
出前係さんは、仕事から帰ってくると、店長に数字を1つだけ言って帰ります。 「0 です」なら無事に配達完了。 それ以外なら、何かあった。
出前係さん「3 です!」
店長「3 って何」
出前係さん「3 です!」
……説明する気が、1ミリもない。 でも、この数字だけで次の段取りを決めるのが、バッチやシェルスクリプトの世界なんです。
結論:まずはこれだけ
先に答えを言うと、終了コードはプログラムが最後に呼び出し元へ返す数字で、0 が成功、0 以外が失敗の合図です。
- 終了コードの確認は、bash なら
$?、cmd(コマンドプロンプト)なら%ERRORLEVEL%、PowerShell なら$LASTEXITCODE - 確認は、コマンドを実行した直後に1回だけ。あとで使うなら変数に保存しておく
- 自分のプログラムで失敗を伝えたいときは、0 以外で終わらせる(Python なら
sys.exit(1))
0 が成功なら、100 は大成功ですか?
大失敗の可能性があります。 出前係さんの「100 です!」は、たぶん100件の苦情です。
じゃあ終了コードを見なくても、ログを読めばよくない?
ログを読むのは人間です。 夜中の3時に、ジョブ管理ツールがログを読んで「これは失敗ですね」と察してくれることはありません。 機械が見るのは、数字だけなんです。
終了コードは「帰り際のひとこと」
出前係さんの言い分を聞くと、数字1つで伝えるから、機械でも判定できるんです。
プログラムが終わるとき、OS はそのプロセス(動いているプログラム1つ分)の終わり方を、呼び出し元に知らせます。 その数字が終了コード(終了ステータス、exit status とも呼びます)です。
bash のマニュアル(Exit Status)には、こう書かれています。
- シェルにとって、終了ステータス 0 で終わったコマンドは成功
- 0 以外は失敗
- 一見あべこべに見えるこのやり方は、成功を表す方法を1つにはっきり決めて、失敗の種類をいろいろ表せるようにするため
つまり、成功は1通り、失敗はいろいろ。
出前係さん「無事に届けました、は1種類です」
出前係さん「失敗は、道に迷った・汁がこぼれた・お客さんが留守だった……」
店長「番号で言って」
……番号で言わせる店長、合理的すぎる。 でも、こうしておくと「1 は迷子、2 は汁こぼれ」のように、失敗の理由を数字で分けられるわけです。
0点が満点って、学生時代に知りたかった。
テストの 0 点は、終了コードとは関係ありません。 残念ですが、あの 0 点は本物です。
確認のしかた:bash・cmd・PowerShell
店長が数字を聞く方法は、シェルごとに入れ物の名前が違うだけです。
bash(Linux・Git Bash)は $?
true echo "true: $?" false echo "false: $?" ls /no/such/udon echo "ls: $?"
true: 0
false: 1
ls: cannot access '/no/such/udon': No such file or directory
ls: 2
(今回の検証環境:Windows 10 Pro の Git Bash、bash 5.2.26)
true は何もせず成功するだけのコマンド、false は何もせず失敗するだけのコマンドです。 $? は、bash のマニュアルで「直前に実行したコマンドの終了ステータス」と説明されている特別な変数です。
存在しないフォルダを ls すると、2 が返りました。 1 ではなく 2。失敗の番号は、コマンドごとに決めてよいんです。
true「成功しました」
false「失敗しました」
おうどん「何をしたの?」
2人「何も」
……何もしていないのに、片方だけ失敗扱い。 でも、if の動きを試すときに、この2人はとても便利です。
cmd(コマンドプロンプト・バッチファイル)は %ERRORLEVEL%
@echo off dir C:\no\such\udon >nul 2>&1 echo dir: %ERRORLEVEL% ver >nul echo ver: %ERRORLEVEL% exit /b 3
このバッチファイル(bat1.bat)を、PowerShell から呼んで確かめました。
cmd /c .\bat1.bat "bat1: LASTEXITCODE=$LASTEXITCODE"
dir: 1
ver: 0
bat1: LASTEXITCODE=3
(Windows PowerShell 5.1 から実行。1つの検証スクリプトからの抜粋です)
- 存在しないフォルダの
dirは 1 ver(Windows のバージョン表示)は成功して 0- バッチの最後の
exit /b 3で、呼び出し元に 3 が返った
ver「Windows のバージョンを言うだけの簡単なお仕事です。0 です」
dir「そんなフォルダありません。1 です」
……同じ出前係でも、配達先によって番号が変わる。 バッチファイルでは、echo %ERRORLEVEL% を1行はさむだけで確認できます。
PowerShell は $LASTEXITCODE
Python のスクリプト(後で出てくる py_exit.py)を PowerShell から呼びます。
python py_exit.py three $ok = $? "python three: LASTEXITCODE=$LASTEXITCODE, ?=$ok" python py_exit.py ok $ok = $? "python ok: LASTEXITCODE=$LASTEXITCODE, ?=$ok"
python three: LASTEXITCODE=3, ?=False
茹で上がりました
python ok: LASTEXITCODE=0, ?=True
(Windows PowerShell 5.1.19041.7725。検証スクリプトからの抜粋です)
$LASTEXITCODEは、最後に動いたネイティブプログラム(python.exe などの外部プログラム)か PowerShell スクリプトの終了コード$?は直前のコマンドが成功したら True、失敗したら False。外部プログラムの場合は、$LASTEXITCODEが 0 なら True、それ以外なら False
終了コード 3 のときは $? が False、0 のときは True。 数字と、成功したかどうかの True/False、2つの入れ物があるわけです。
PowerShell「数字で知りたい人は LASTEXITCODE、はい・いいえで知りたい人は $? へどうぞ」
おうどん「窓口が2つある……」
……役所みたいな親切さ。 2つの窓口の違いは、中級編でもう一度出てきます。
$? は、直前の1回分しか覚えていない
ここがいちばんハマるところです。$? は、次のコマンドを実行した瞬間に上書きされます。
ls /no/such/udon echo "ls の結果を表示します" echo "exit=$?"
ls: cannot access '/no/such/udon': No such file or directory
ls の結果を表示します
exit=0
ls は失敗したのに、exit=0。 $? が見ているのは ls ではなく、間にはさんだ echo の結果(成功)だからです。
店長「さっきの出前、どうだった?」
店長の記憶「直前に聞いたのは、隣の人の『いらっしゃいませ』です」
……店長の記憶、1人分しか入らない。 あとで使いたいなら、すぐにメモ(変数)に書いておきます。
ls /no/such/udon 2>/dev/null rc=$? echo "ls の結果を表示します" echo "exit=$rc"
ls の結果を表示します
exit=2
rc=$? で、ls の直後に番号を保存しました。 これで、間に何をはさんでも 2 が残ります。
PowerShell の例で $ok = $? と書いていたのも、同じ理由です。 (2>/dev/null は、エラーメッセージを捨てる書き方です。この例では表示を短くするために付けています)
毎回 rc=$? って書くの、めんどくさくない?
めんどくさいです。 でも、書かなかった日に限って、exit=0 の嘘を信じて朝を迎えます。
Python で終了コードを返す
自分でプログラムを書くときは、出前係さんの側になります。Python では sys.exit() で番号を決めます。
import sys
mode = sys.argv[1]
if mode == "ok":
print("茹で上がりました")
elif mode == "three":
sys.exit(3)
elif mode == "msg":
sys.exit("設定ファイルがありません")
elif mode == "boom":
print(1 / 0)
elif mode == "big":
sys.exit(256)
これを py_exit.py として、Git Bash から呼びます。
for m in ok three msg boom big; do python py_exit.py "$m" echo "$m -> $?" done
茹で上がりました
ok -> 0
three -> 3
設定ファイルがありません
msg -> 1
Traceback (most recent call last):
File "...\py_exit.py", line 11, in <module>
print(1 / 0)
~~^~~
ZeroDivisionError: division by zero
boom -> 1
big -> 0
(Python 3.13.1。トレースバックのファイルの場所は ... に省略しています)
- 最後まで普通に終わると 0
sys.exit(3)は 3sys.exit("メッセージ")は、メッセージをエラー出力に出して 1- 例外で落ちると 1
Python の sys.exit のドキュメントにも、整数なら 0 が成功でそれ以外は異常終了、整数以外のもの(None を除く)を渡すとエラー出力(stderr)に表示されて終了コードが 1 になる、と書かれています。 sys.exit("メッセージ") は、エラー時にすばやく終わるための書き方として紹介されています。 引数を省略したときや None を渡したときは、0 になります。
最後の big -> 0 って何? 256 で終わらせたのに?
……いいところに気づきました。 それは上級編で。 256 件の失敗を報告したのに、なぜか「0(成功)」として記帳される事件です。
例外で落ちても 1 になるなら、sys.exit いらなくない?
「落ちた」と「ちゃんと失敗を報告した」は別物です。 トレースバック(エラーの発生場所の一覧)を読むのは人間なので、「設定ファイルがありません」と1行で言ってくれるほうが、夜中の人に優しいんです。
🔰 ここまで読めば今日から困らない
- 終了コードは、プログラムが最後に返す数字。0 が成功、0 以外は失敗の合図
- 確認は bash なら
$?、cmd なら%ERRORLEVEL%、PowerShell なら$LASTEXITCODE $?は次のコマンドで上書きされる。すぐにrc=$?のように保存する- Python で失敗を伝えるなら
sys.exit(1)やsys.exit("理由")
4行。付箋に書いて、モニターに貼っておきます。
付箋を貼ったら、その付箋の終了コードは 0 です。 貼れなかったら……たぶん 1 です。
4行も覚えられないので、0 が成功ってことだけ覚えます。
それだけ覚えて $? を見る前に echo をはさむと、毎回 0 が出て、毎回安心して、毎回だまされます。 3行目の「すぐ保存」まではセットでお願いします。
実践編はどこ?
終了コードを実際の現場で読む話は、こちらにまとめています。
- robocopy が成功したのに失敗扱い? 終了コード1の意味と「8以上だけ失敗」にする書き方
- Bash で処理が失敗したのに成功扱い? pipefail・PIPESTATUS で終了コードを確かめる
- Git の「LF will be replaced by CRLF」警告の意味と対処(
cdに失敗したのに終了コードが 0 になる例が出てきます)
ここから先は中級者向け。読み飛ばしてもOKです。
中級:0 以外が、いつもエラーとは限らない
「0 以外は失敗」は、あくまで合図であって、エラーとは限りません。代表例が grep です。
printf 'kitsune\ntanuki\n' > menu.txt grep kitsune menu.txt echo "found: $?" grep tempura menu.txt echo "not found: $?" grep kitsune nofile.txt echo "no file: $?"
kitsune
found: 0
not found: 1
grep: nofile.txt: No such file or directory
no file: 2
(今回の検証環境の Git Bash、GNU grep 3.0)
GNU grep のマニュアルでは、行が見つかれば 0、見つからなければ 1、エラーが起きたら 2 と説明されています。
つまり grep の 1 は「エラー」ではなく「見つかりませんでした」という報告です。
grep「天ぷら、メニューにありませんでした(1)」
監視ツール「異常終了を検知しました!!」
grep「いや、ないって言っただけ……」
……報告しただけで、非常ベルを鳴らされる。 「エラーが 0 件なら OK」のチェックに grep を使うと、エラーが見つからなかった日(いい日)に限って失敗扱いになります。
逆に、robocopy のように 1 が成功を意味するコマンドもあります。 0 以外をどう読むかは、コマンドごとのドキュメントで確かめるのが基本です。
じゃあ、全部のコマンドの番号表を暗記しないといけない?
暗記はいりません。 使うコマンドの「Exit Status」の欄を、使う前に1回読むだけです。 うどん屋さんでも、初めてのお店ではメニューを見ますよね。
中級:終了コードで次の処理を分ける(&&・||・if)
シェルの &&・||・if は、全部、終了コードを見て動いています。
printf 'kitsune\ntanuki\n' > menu.txt mkdir -p udon cd udon && echo "cd できたので続けます" cd soba && echo "cd できたので続けます" cd soba || echo "cd に失敗したので止めます" if grep -q kitsune ../menu.txt; then echo "きつねあります" else echo "きつねありません" fi
cd できたので続けます
b7.sh: line 4: cd: soba: No such file or directory
b7.sh: line 5: cd: soba: No such file or directory
cd に失敗したので止めます
きつねあります
(上のコードを b7.sh というファイルにして実行した結果です)
A && B:A が 0 で終わったら B を実行A || B:A が 0 以外で終わったら B を実行if コマンド; then:コマンドの終了コードが 0 なら then 側
なお、上の cd soba || echo は「止めます」と表示するだけで、スクリプト自体は止まりません。次の if も実行されます。
if のうしろに書くのは、条件式ではなくコマンドです。 grep -q は何も表示せず、見つかったかどうかを終了コードだけで返すオプション。if と組み合わせる定番です。
bash のマニュアルの Exit Status にも、終了ステータスは条件分岐やリストの構文で使われる、と書かれています。
if「0 なら then、それ以外なら else。以上です」
おうどん「true/false じゃないの?」
if「0 か、それ以外か。それだけです」
……二択の選び方が、数字の世界の人。
実務でよく使うのは、cd の失敗で止める書き方です。
cd /path/to/work || exit 1
(書き方の例です。このブロックは単体では実行していません)
cd に失敗したら、その場でスクリプトを終える。 失敗したまま別のフォルダで rm が走る、という最悪のパターンを防げます。
&& と || をつなげて、A && B || C って書けば if の代わりになる?
B が失敗したときにも C が動きます。 「A が成功したら B、失敗したら C」のつもりで書くと、B が 0 以外で終わった日に C まで動くので、分岐をはっきりさせたいときは if を使いましょう。
中級:呼び出す側で受け取る(Python・cmd)
スクリプトから別のプログラムを呼ぶときは、受け取る側がちゃんと番号を見ているかが大事です。
Python の subprocess
import subprocess
import sys
r = subprocess.run([sys.executable, "py_exit.py", "three"])
print("returncode:", r.returncode)
try:
subprocess.run([sys.executable, "py_exit.py", "three"], check=True)
except subprocess.CalledProcessError as e:
print("止めました:", e)
returncode: 3
止めました: Command '['C:\\Users\\aria_\\AppData\\Local\\Programs\\Python\\Python313\\python.exe', 'py_exit.py', 'three']' returned non-zero exit status 3.
(python -X utf8 py_caller.py で実行した結果です。sys.executable は今動いている Python 本体の場所です)
subprocess.run()は、終了コードが 0 以外でも、例外を出さずにreturncodeに入れて返すだけcheck=Trueを付けると、0 以外のときにCalledProcessErrorという例外になった
subprocess「3 でした。お伝えしましたよ?」
おうどん「……returncode、見てなかった」
……伝えたのに、聞いてもらえていない。 check=True を付けるか、returncode を自分で見るか。どちらかは必ずやりましょう。
cmd の if errorlevel は「以上」
@echo off cmd /c exit 0 echo --- ERRORLEVEL=%ERRORLEVEL% if errorlevel 0 echo if errorlevel 0 : true if errorlevel 1 echo if errorlevel 1 : true if %ERRORLEVEL% NEQ 0 echo NEQ 0 : true cmd /c exit 3 echo --- ERRORLEVEL=%ERRORLEVEL% if errorlevel 1 echo if errorlevel 1 : true if %ERRORLEVEL% NEQ 0 echo NEQ 0 : true
--- ERRORLEVEL=0
if errorlevel 0 : true
--- ERRORLEVEL=3
if errorlevel 1 : true
NEQ 0 : true
(bat2.bat として、PowerShell から cmd /c .\bat2.bat で実行した結果です)
ERRORLEVEL=0(成功)なのに、if errorlevel 0 が真になりました。 if errorlevel N は「N と等しい」ではなく「N 以上」の意味だからです(詳しくは robocopy の記事で Microsoft のドキュメントとあわせて解説しています)。
if errorlevel 0「0 以上ですね。はい、真です」
おうどん「0 以上って、ほぼ全部では」
……実際、マイナスの終了コード以外は全部です。 正の終了コードを調べるなら if errorlevel 1、負の値も含めて 0 以外を調べるなら if %ERRORLEVEL% NEQ 0 のように比べる書き方にしましょう。 (ここではコマンド拡張が有効で、ERRORLEVEL という環境変数を自分で定義していないことが前提です)
中級:PowerShell の $? と $LASTEXITCODE は別の係
PowerShell では、$LASTEXITCODE は外部プログラムとスクリプトの係、$? はコマンド全般の係です。
powershell -NoProfile -File .\child.ps1 exit7 "child exit7: LASTEXITCODE=$LASTEXITCODE" powershell -NoProfile -File .\child.ps1 throw 2>$null "child throw: LASTEXITCODE=$LASTEXITCODE" powershell -NoProfile -File .\child.ps1 ok "child ok: LASTEXITCODE=$LASTEXITCODE" Get-Item C:\no\such\udon -ErrorAction SilentlyContinue $ok = $? "Get-Item: ?=$ok, LASTEXITCODE=$LASTEXITCODE"
child.ps1 の中身はこうです。
param([string]$Mode)
if ($Mode -eq "exit7") { exit 7 }
if ($Mode -eq "throw") { throw "dashi ga kireta" }
"finished normally"
child exit7: LASTEXITCODE=7
child throw: LASTEXITCODE=1
finished normally
child ok: LASTEXITCODE=0
Get-Item: ?=False, LASTEXITCODE=0
(検証スクリプトからの抜粋です)
powershell -Fileで呼んだスクリプトがexit 7→ 7- 例外(
throw)で終わった → 1 - 最後まで普通に終わった → 0
Get-Item(PowerShell のコマンドレット)が失敗 →$?は False だが、$LASTEXITCODEは前の 0 のまま
この 3 パターンは、PowerShell の自動変数のドキュメントにある -File で呼んだときの決まりと同じです。
最後の行が大事です。 コマンドレットの失敗は、$LASTEXITCODE を変えません。 $LASTEXITCODE だけで判定していると、Get-Item や Copy-Item の失敗を見落とします。
Get-Item「失敗しました」
$LASTEXITCODE「え、僕は外部プログラムの係なので。さっきの 0 のままです」
……担当外のことは、1ミリも覚えない。 外部プログラムは $LASTEXITCODE、コマンドレットは $? や -ErrorAction Stop と try で、と係を分けて見ましょう。
全部 $? で見ればいいのでは?
外部プログラムの番号(robocopy の 1 のような)を知りたいときは、$? の True/False では足りません。 窓口が2つあるのは、ちゃんと理由があるんです。
ここから先は上級者向け。読み飛ばしてもOKです。
上級:終了コードは 0〜255。256 は 0 に戻る
POSIX(UNIX 系 OS の共通仕様)の wait()・waitpid() で受け取る終了コードや、bash の $? で見る値は、下位 8 ビット(0〜255)の範囲です。
for n in 0 1 255 256 257 300 -1; do bash -c "exit $n" echo "exit $n -> $?" done
exit 0 -> 0
exit 1 -> 1
exit 255 -> 255
exit 256 -> 0
exit 257 -> 1
exit 300 -> 44
exit -1 -> 255
(今回の検証環境の Git Bash、bash 5.2.26)
256 は 0、257 は 1、300 は 44(300 − 256)、-1 は 255。 256 で割った余りのような動きです。
POSIX の exit() の仕様には、wait() と waitpid()(親プロセスが子の終了を受け取る関数)から取れるのは、status の下位 8 ビット(status & 0377)だけと書かれています。 (同じ仕様には、waitid() などでは全体の値が取れる、とも書かれています) bash のマニュアルも、終了ステータスは 0〜255 の範囲としています。
出前係さん「256 件の苦情が来ました!」
店長の伝票「256……桁があふれたので 0。無事配達、と」
……いちばん悪い日に、いちばんいい記録が残る。 「失敗した件数をそのまま終了コードにする」設計は、256 件目で成功になるので避けましょう。
Python のドキュメントでも、多くのシステムでは 0〜127 の範囲にする必要があり、それ以外は結果が定まらない、とされています。 また、Unix のプログラムでは、コマンドラインの書き方の誤りに 2、それ以外のエラーに 1 を使うのが一般的、とも書かれています。
じゃあ 127 までで、失敗の種類を 127 通り作れる!
作れますが、覚えるのは誰でしょう。 たぶん半年後のおうどんで、たぶん覚えていません。 次の節のとおり、126 以上は bash が特別な意味で使うので、なおさら避けたほうが安全です。
上級:126・127・128+N は bash が使う特別な番号
bash のマニュアルには、シェル側が特別な意味で使う番号が決まっています。
- 127:コマンドが見つからない
- 126:コマンドは見つかったが実行できない
- 128+N:番号 N のシグナル(OS からプロセスへの割り込みの合図)で終了した
udonn echo "typo: $?" printf 'echo hi\n' > noexec.txt ./noexec.txt echo "noexec: $?" bash -c 'kill -TERM $$' echo "TERM: $?" bash -c 'kill -KILL $$' echo "KILL: $?"
b5.sh: line 1: udonn: command not found
typo: 127
hi
noexec: 0
Terminated
TERM: 143
b5.sh: line 8: 701 Killed bash -c 'kill -KILL $$'
KILL: 137
(b5.sh として Git Bash で実行した結果です。701 はプロセス番号で、実行のたびに変わります)
udonn(打ち間違い)→ 127- TERM(15番のシグナル、終了のお願い)で終わった → 128+15 = 143
- KILL(9番のシグナル、強制終了)で終わった → 128+9 = 137
137 や 143 を見たら、「プログラムが自分で失敗した」のではなく「外から止められた」可能性を考えます。 (ただし、プログラムが自分で exit 137 と返すこともできるので、番号だけで決めつけないようにしましょう)
出前係さん「137 です」
店長「そんな番号、決めた覚えないけど」
出前係さん「途中で、強制的に帰らされました」
……本人の失敗じゃなかった。
そして、今回の検証環境で1つ予想と違ったのが noexec: 0 です。 実行権限を付けていないファイルを実行したのに、Git Bash ではそのまま動いて 0 になりました。 Linux なら 126 が返る場面ですが、Git Bash は Windows 上で動いていて、実行できるかどうかの判断が Linux と同じではないようです(理由は今回確かめていません)。 126 は、今回の検証環境では再現できませんでした。
Git Bash、また優しい……
前回の改行コードの記事でも、Git Bash だけが CR を見逃していました。 Git Bash で動いたことは、Linux で同じ番号が返る証明にはなりません。
上級:Windows は 256 も -1 も返せる。でも Git Bash を通すと……
Windows の終了コードは、8 ビットに切り詰められずに届きます。
python py_exit.py big "python big: LASTEXITCODE=$LASTEXITCODE" cmd /c exit 256 "cmd exit 256: LASTEXITCODE=$LASTEXITCODE" cmd /c exit -1 "cmd exit -1: LASTEXITCODE=$LASTEXITCODE"
python big: LASTEXITCODE=256
cmd exit 256: LASTEXITCODE=256
cmd exit -1: LASTEXITCODE=-1
(Windows PowerShell 5.1 から実行。検証スクリプトからの抜粋です)
PowerShell から見ると、256 は 256、-1 は -1 のまま届きました。
ところが、同じ sys.exit(256) を Git Bash から呼んだときは、Python の節で見たとおり big -> 0 でした。
つまり今回の検証環境では、
- PowerShell から呼ぶ → 256
- Git Bash から呼ぶ → 0
同じプログラム、同じ番号なのに、受け取る側によって成功と失敗が入れ替わったんです。 Git Bash の bash が、受け取った値を 0〜255 の範囲で扱っているためと考えられます(Git Bash が内部でどう変換しているかまでは確認していません)。
python「256 です」
PowerShell「256 ですね、失敗」
Git Bash「0 ですね、成功」
……同じ報告を聞いて、店長2人の判断が真逆。 Windows のジョブから呼ぶのか、Git Bash や WSL から呼ぶのかで、結果が変わりうるということです。
移植性を考えるなら、自分のプログラムの終了コードは 0〜125 の小さな数にしておくのが安全です。 (126 以上は、前の節の特別な番号とぶつかります)
-1 で「致命的なエラー」を表すのはどう?
PowerShell では -1 のまま見えますが、bash の世界に入ると 255 です。 -1 のつもりが 255。うどんを1玉返したつもりが、255玉の請求書になって届くようなものです。
チェックリスト
- コマンドを実行した直後に、
$?・%ERRORLEVEL%・$LASTEXITCODEで終了コードを確認している - あとで使う終了コードは、すぐに
rc=$?(PowerShell なら$rc = $LASTEXITCODE)で保存している - 自分のプログラムは、失敗時に 0 以外(1〜125 の範囲)で終わる
- 使うコマンドの終了コードの意味を、ドキュメントで確かめた(grep の 1、robocopy の 1 など)
- Python から外部プログラムを呼ぶときは、
check=Trueかreturncodeの確認をしている - cmd の
if errorlevel Nは「N 以上」だと理解して書いている - PowerShell では、外部プログラムは
$LASTEXITCODE、コマンドレットは$?やエラー処理で見ている - 137・143 を見たら、外から止められた可能性(タイムアウト・強制終了)を調べる
8個。終了コードなら 8 は立派な失敗の番号です。
1個目と2個目だけでも、明日の朝の「エラーは出ていないのに止まっていた」はかなり減ります。 残りの6個は、来週のおうどんが確認します。
……来週のおうどん「終了コード 1。聞いてない」
まとめ
冒頭では、夜間バッチが止まっていたのに、エラーはどこにも出ていませんでした。
でも、出前係さんはちゃんと数字を言っていたんです。 店長が、その数字を聞いていなかっただけ。
出前係さん「ずっと 3 って言ってました」
店長「……ずっと聞こえてた。意味がわからなかっただけ」
……聞こえていても、読み方を知らなければ聞いていないのと同じ。 番号の意味は、ドキュメントに書いてあります。
- 0 が成功、0 以外が失敗の合図
$?は直前の1回分しか覚えていない- 0 以外の意味はコマンドごとに違う
- 256 は 0 に戻ることがある
終了コードは、プログラムが最後に言うひとこと。聞く側が、直後にちゃんと聞いてあげましょう。
おうどん「よし、全部のスクリプトの全部の行に echo $? を入れる!」
……各コマンドの直後に1回ずつなら、その結果は見られます。 でも echo $? を連続で入れると、2回目からは echo 自身の成功を報告します。 報告係の自己紹介が、止まらない。
まずは、いちばん大事なスクリプトの最後の行のあとで、1回だけ echo $? を打ってみてください。 0 が返ってきたら、今夜は安心してかけうどんです🍜
参考資料
- GNU Bash Reference Manual:Exit Status — 0 が成功で 0 以外が失敗であること、その設計の理由、0〜255 の範囲、127・126・128+N、
$?、条件分岐やリストで使われること - POSIX(The Open Group Base Specifications):exit —
wait()・waitpid()から取れるのは status の下位 8 ビット(status & 0377)であること - Python ドキュメント:sys.exit — 0 が成功、
None以外の非整数は stderr に出して 1、Noneは 0、0〜127 の範囲、2 と 1 の慣習 - Microsoft Learn:about_Automatic_Variables(PowerShell 5.1) —
$?と$LASTEXITCODEの意味、外部プログラムの$?、-Fileで呼んだときの値 - GNU Grep Manual — 見つかれば 0、見つからなければ 1、エラーは 2
確認日:2026-09-28。

コメント