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

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

この記事でできるようになること

  • `echo $?` で直前のコマンドが成功したか失敗したかを確認できる
  • `&&` と `||` で「成功したら」「失敗したら」の処理を書き分けられる
  • `exit 0` と `exit 1` で、成功と失敗を呼び出し元に伝えられる

前提知識(先に読むと理解しやすい記事)

この記事で学べること

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

言葉の整理

この記事で使う ls / echo / cd / mkdir は、既存のファイルを消しません。安心して試してください。

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

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

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

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

リナ: 先輩、コマンドを実行すると結果が画面に出ますよね。でも「成功したのか失敗したのか」は、どうすればわかるのでしょうか。
ライニー先輩: いいところに気づいたね。実は コマンドは終わるときに、必ず数字を 1 つ返している んだ。それを 終了ステータス と呼ぶよ。
リナ: 数字ですか。画面には出ていないようですが。
ライニー先輩: ふだんは見えないところに隠れているんだ。宅配便の伝票を想像してみて。荷物そのものとは別に、「届いた」「不在だった」という記録が残るよね。
ライニー先輩: それと同じで、コマンドも結果とは別に記録を残す。ルールは単純で、0 なら成功、0 以外なら失敗 だよ。

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

数字 意味
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 の結果を表示します。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 になる
リナ: 失敗したはずのコマンドなのに 0 が出ました。終了ステータスが壊れているのでしょうか。
ライニー先輩: 壊れてないよ。$? は直前の 1 つ分しか覚えていないんだ。
リナ: えっ、間に挟んだ pwd の結果に置きかわっていたんですか。
ライニー先輩: そう。pwd は成功するから 0 になる。ls の結果はもう上書きされているんだ。
リナ: 見たかった結果が消えていたんですね。納得しました。すぐ次で見るようにします。

確認したいコマンドの すぐ次$? を見てください。間に別のコマンドを挟むと、$? の中身が上書きされます。

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

スクリプトの中で、成功したか失敗したかを呼び出し元に伝えたいときは exit を使います。

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

exit に数字を付けない場合は、直前のコマンドの終了ステータス がそのまま返ります。

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

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

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

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

ヒント 1(方向づけ)を見る

直前の結果は、シェルが自動で用意する特別な変数に入っています。その中身を画面に出します。

ヒント 2(コマンド名)を見る

変数は $? です。表示は echo です。

答えを見る
$ ls
$ echo $?
0

ls が成功したので 0 が出ます。存在しないディレクトリファイルをまとめて整理する入れ物。Windows や macOS の「フォルダ」と同じもの。を指定すると、0 以外の数字に変わります。

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

ヒント 1(方向づけ)を見る

2 つのコマンドを 1 行につなぎます。左が成功したときだけ右が動く記号を使います。

ヒント 2(コマンド名)を見る

使う記号は && です。表示は echo です。

答えを見る
$ mkdir test-dir && echo "作成成功"
作成成功

2 回目に同じコマンドを実行すると、mkdir が失敗します。そのため「作成成功」は表示されません。試したディレクトリは rmdir test-dir で削除できます。

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

ヒント 1(方向づけ)を見る

課題 2 と同じ形ですが、記号が逆になります。左が失敗したときだけ右が動く記号を使います。

ヒント 2(コマンド名)を見る

使う記号は || です。移動は cd です。

答えを見る
$ cd /not-exist || echo "移動できません"
bash: cd: /not-exist: そのようなファイルやディレクトリはありません
移動できません

cd 自体のエラーメッセージと、自分で出したメッセージの両方が表示されます。

9. 振り返り

リナ: 整理します。コマンドは終わるときに数字を返す。0 が成功で、それ以外が失敗ですね。
ライニー先輩: そのとおり。その数字は echo $? で見られる。ただし直前の 1 回分だけだよ。
リナ: そして && は成功したら次へ、|| は失敗したら次へ。ここまで覚えました。
ライニー先輩: 完璧だね。あとは自分のスクリプトの最後に exit 0exit 1 を書き分けてみて。

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

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

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

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

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

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

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

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

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

今日の 3 行まとめ

  1. コマンドは終わるときに数字を返す。0 が成功、それ以外は失敗
  2. 直前の結果は echo $? で確認する。間に別のコマンドを挟まない
  3. && は成功したら次へ進む。|| は失敗したら次へ進む

次に読む

この記事を共有

次の一手