終了ステータス(exit status)入門 - $? と && || で処理を分岐する

終了ステータス(exit status)入門 - $? と && || で処理を分岐する

この記事で学べること

  • コマンドの 成功・失敗を表す「終了ステータス(exit status)」 という考え方が分かる
  • $?直前のコマンドの結果 を確認できるようになる
  • &&||「成功したら」「失敗したら」 の処理を分岐できる
  • シェルスクリプトで エラーをきちんと扱う第一歩 が踏み出せる

結論(先に覚えるべき型)

  • コマンドの結果は 数字 で返ってくる(0 が成功、それ以外が失敗)
  • 直前の結果を見たい → echo $?
  • 成功したら次へ&&
  • 失敗したら次へ||

1. 終了ステータスとは何か?

結論: 終了ステータスはコマンドが返す数字。0 が成功、1255 が失敗を表す。

リナ: 先輩、コマンドって実行したら結果が画面に出ますよね。でも「成功したのか失敗したのか」はどうやって分かるんですか?
ライニー先輩: いいところに気づいたね。実は コマンドは終わるときに必ず「数字」を1つ返している んだ。それを 終了ステータス(exit status / exit code) と呼ぶよ。
リナ: 数字...? 画面には出てないですけど。
ライニー先輩: 普段は見えないところに隠れているんだ。ルールはシンプルで、0 なら成功、0 以外なら失敗。この1個の数字でコマンドの成否を判断できる。

終了ステータスの基本ルール

数字 意味
0 成功
1255 失敗(何らかの異常)

0 が成功」という点が直感に反するので、最初に必ず覚えること。

2. なぜ終了ステータスが重要なのか?

結論: 終了ステータスがあるから「成功したときだけ次を実行」といった自動化ができる。

リナ: 数字が返っているのは分かりました。でも、それって何の役に立つんですか?
ライニー先輩: たとえば「バックアップが成功したときだけ、古いファイルを消す」みたいな処理を考えてごらん。失敗したのに消したら大事故だよね。
リナ: たしかに...! 成功したかどうかで次の動きを変えたいです。
ライニー先輩: そう。その「成功したかどうか」の判断に使うのが終了ステータスなんだ。だから自動化やシェルスクリプトを書くときの 土台 になる。

画面の表示だけで成否を判断するのは危険。たとえば「Error」という文字が出ていなくても失敗していることはある。機械的に判断できる終了ステータスを使う のが確実。

3. $? で結果を確認する

結論: $? には直前のコマンドの終了ステータスが入る。echo $? で中身を見られる。

直前に実行したコマンドの終了ステータスは、特別な変数 $? に入っている。

$ ls /etc
hosts  passwd  ...
$ echo $?
0
リナ: 0 が出ました! ls が成功したってことですね。
ライニー先輩: その通り。じゃあ次は、わざと失敗させてみよう。存在しないファイルを ls してみて。
$ ls /not-exist
ls: '/not-exist' にアクセスできません: そのようなファイルやディレクトリはありません
$ echo $?
2
リナ: 今度は 2 が出ました。さっきと数字が違う...!
ライニー先輩: 失敗のときは 0 以外が返る。ls は「ファイルが見つからない」エラーで 2 を返す決まりなんだ。数字の中身までは覚えなくていい。0 か、それ以外か だけ見れば十分だよ。

$?直前のコマンドの結果 しか保持しない。echo $? を2回続けると、2回目は「1回目の echo の終了ステータス(成功なので 0)」を表示してしまう。確認は1回だけにすること。

4. && で「成功したら次」をつなぐ

結論: cmd1 && cmd2 は cmd1 が成功(0)したときだけ cmd2 を実行する。

&&「左が成功したら、右も実行する」 という記号。

$ mkdir backup && cp data.txt backup/
リナ: これは mkdir が成功したら cp する、という意味ですか?
ライニー先輩: ばっちり。もし mkdir backup が失敗したら(たとえば権限がなくて作れなかったら)、cp実行されない。「前の処理が成功したことが前提」のときに使うんだ。
# ビルドが成功したときだけテストを走らせる
$ make && make test

# ディレクトリへ移動できたときだけ中身を表示
$ cd /var/log && ls

&& は「前提条件が満たされたら進む」イメージ。失敗したらそこで止まってほしい 処理を安全につなげる。

5. || で「失敗したら次」をつなぐ

結論: cmd1 || cmd2 は cmd1 が失敗したときだけ cmd2 を実行する。エラー時の対処に使う。

||&& の逆で、「左が失敗したら、右を実行する」 という記号。

$ cd /var/log || echo "ディレクトリへ移動できませんでした"
リナ: cd が失敗したらメッセージを出す、ということですね。成功したらメッセージは出ない?
ライニー先輩: その通り。cd が成功すれば echo は実行されない。||「うまくいかなかったときの保険」 として使うことが多いよ。
# コマンドが無ければインストールを促す
$ which jq || echo "jq が入っていません。インストールしてください"

# 処理に失敗したらスクリプトを終了
$ cp data.txt backup/ || exit 1

&& と || の対比

cmd1 && cmd2   →  cmd1 が【成功】したら cmd2
cmd1 || cmd2   →  cmd1 が【失敗】したら cmd2

&& は成功で進む」「|| は失敗で進む」と覚える。

6. && と || を組み合わせる

結論: cmd && 成功時 || 失敗時 で「成功なら A、失敗なら B」を1行で書ける。

&&|| をつなげると、成功・失敗で別々の処理を書ける。

$ ping -c1 example.com > /dev/null && echo "接続OK" || echo "接続NG"
リナ: 「成功したら接続OK、失敗したら接続NG」を1行で書けるんですね!
ライニー先輩: そう。ただし注意点がある。この書き方は 「成功時の処理(echo 接続OK)が失敗すると、失敗時の処理まで動いてしまう」 という落とし穴があるんだ。簡単なメッセージ表示なら問題ないけど、複雑な処理では if 文を使う方が安全だよ。

A && B || C は厳密には「if A then B else C」と完全に同じではない。B が失敗すると C も実行される。確実に分岐したいときは if 文(次に読む記事を参照)を使うこと。

7. よくある初心者のつまずき

結論: 0 が成功という点と、$? が直前専用という点を取り違えやすい。

7-1. 「0 が成功」を逆に覚える

リナ: 正直、0 が成功って今でも違和感あります...。
ライニー先輩: みんなそう。「0 = エラーなし = 問題ゼロ」 と考えると覚えやすいよ。テストの点数ではなく「エラーの個数」だとイメージするといい。

7-2. $? を取るタイミングが遅い

$ ls /not-exist
$ pwd            # ← 余計なコマンドを挟んでしまった
$ echo $?        # これは pwd(成功)の結果 0 になる

確認したいコマンドの すぐ次$? を見ること。間に別のコマンドを挟むと $? が上書きされる。

7-3. 終了ステータスを自分で返したい

スクリプトやコマンドの中で、成功・失敗を呼び出し元に伝えたいときは exit を使う。

exit 0   # 成功として終了
exit 1   # 失敗として終了

exit に数字を付けないと 直前のコマンドの終了ステータス がそのまま返る。

8. ミニ課題:実際にやってみよう

結論: 確認・成功時実行・失敗時実行の3問で、$? と && || を手で確かめる。

リナ: 知識は入りました! 手を動かしてみたいです。
ライニー先輩: いいね、3問用意したよ。ターミナルで試してみて。

課題1: 適当なコマンド(例: ls)を実行した直後に、その終了ステータスを画面に表示しよう。

ヒントを見る

直前の結果は $? に入っている。

解答例
$ ls
$ echo $?

課題2: mkdir test-dir が成功したときだけ「作成成功」と表示しよう。

ヒントを見る

「成功したら次」は &&

解答例
$ mkdir test-dir && echo "作成成功"

課題3: 存在しないディレクトリへ cd を試み、失敗したら「移動できません」と表示しよう。

ヒントを見る

「失敗したら次」は ||

解答例
$ cd /not-exist || echo "移動できません"

9. コピペ用テンプレート

結論: 確認・成功時・失敗時・成否分岐・終了のよく使う型をまとめて手元に置いておく。

よく使う型をまとめておく

# 直前の終了ステータスを確認
echo $?

# 成功したときだけ次を実行
コマンドA && コマンドB

# 失敗したときだけ次を実行
コマンドA || コマンドB

# 成功なら A、失敗なら B(簡易分岐)
コマンド && echo "OK" || echo "NG"

# 失敗したらスクリプトを終了
コマンド || exit 1

# スクリプトを明示的に成功 / 失敗で終わらせる
exit 0   # 成功
exit 1   # 失敗

次に読む