【おうどんの基礎講座】終了コードってなに? 0が成功の理由と$?・%ERRORLEVEL%・$LASTEXITCODEの確認方法

【おうどんの基礎講座】終了コードってなに?

こんにちは、おうどんです🍜

夜間バッチが朝に止まっていた。 ログを見る。

エラーは、出ていない。

先輩「終了コードは見た?」

おうどん「……しゅうりょうこーど?」

先輩「プログラムが最後に返す数字。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。検証スクリプトからの抜粋です)

PowerShell の自動変数のドキュメントによると、

  • $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) は 3
  • sys.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行目の「すぐ保存」まではセットでお願いします。

実践編はどこ?

終了コードを実際の現場で読む話は、こちらにまとめています。

ここから先は中級者向け。読み飛ばしても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 が返ってきたら、今夜は安心してかけうどんです🍜

参考資料

確認日:2026-09-28。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

おうどん|癒しと創作をたのしむ雑食クリエイター
写経アプリやクレイセラピー、優しい和風デザインがすき。
ZARDと刀剣と文字に癒されて、今はアプリ作ってます。

コメント

コメントする

目次