bash で set -e を書いてもエラーでスクリプトが止まらない
set -e には仕様上の適用外がある。
条件文脈で実行されたコマンド、パイプラインの途中、local や export と同時に書いた代入では終了ステータスが無視される。
止めたい箇所を明示的に判定する。
公開: 更新:
bash pipe.sh
bash local_bad.sh local と同じ行: 止まらずに続行
2026-10-03 に bash:5(Bash 5.3.20) で実際に打って取った出力。検証環境の詳細
要約
set -e(errexit)は「あらゆる失敗で止まる」設定ではない。終了ステータスが何かの判定に使われる位置では、失敗しても止まらないと決められている。
次のスクリプトは A から D まで全部表示される。
set -e
f() { false; echo "in-f-after-false"; }
if f; then :; fi
echo "A"
false || echo "B"
! false
echo "C"
false | true
echo "D"条件文脈で呼ばれた f は、中の false でも止まらず次の echo まで進む。
関数の内側も丸ごと適用外になる点が、特に見落としやすい。
実行例
記事冒頭のスクリプトを bash:5(Bash 5.3.20)で動かすと、関数の中の false でも止まらず in-f-after-false と A から D までが全部表示され、終了コードは 0 だった。set -eo pipefail を付けたパイプラインと local の宣言・代入を分けた関数は終了コード 1 で止まり、local out=$(false) と同じ行に書いた版は止まらずに続行しており、ERR トラップは failed: line 4 (exit 1) を出している。
$ bash demo.sh
in-f-after-false
A
B
C
D
$ echo $?
0$ bash pipe.sh
$ echo $?
1$ bash local_bad.sh
local と同じ行: 止まらずに続行
$ echo $?
0$ bash local_ok.sh
$ echo $?
1$ bash trap.sh
start
ls: /no/such/dir: No such file or directory
failed: line 4 (exit 1)
$ echo $?
1$ bash -c "set -e; if false; then :; fi; echo reached"
reached— 2026-10-03 時点の出力
検証環境Bash 5.3.20 / 2026-10-03 検証
- 検証日
- 実行環境
bash:5Alpine Linux v3.24- バージョン
- Bash 5.3.20
この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。
解決策
1. pipefail を足す
set -eo pipefail
curl -fsSL https://example.com/archive.tar.gz | tar xzpipefail を付けると、パイプラインのどれか 1 つでも失敗すれば全体が失敗になる。curl には -f も付けて、HTTP エラーを終了ステータスに反映させる。
2. 宣言と代入を分ける
local out
out=$(some_command)こう書けば some_command の終了ステータスが代入文のものとして評価され、set -e が効く。
関数内で local を使うときの定石である。
3. 止めたい場所を明示する
some_command || { echo "some_command に失敗した" >&2; exit 1; }適用外の条件を暗記して回避するより、重要な箇所だけ自分で判定するほうが読み手にも意図が伝わる。
4. ERR トラップで位置を出す
set -eo pipefail
trap 'echo "failed: line $LINENO (exit $?)" >&2' ERRERR トラップは set -e が止める条件と同じ場面で発火する。
どこで落ちたのか分からないスクリプトの調査に使える。
5. 挙動を先に確かめる
bash -c 'set -e; if false; then :; fi; echo reached'reached が出れば、その書き方は適用外だと確認できる。
仕様の詳細は Bash Reference Manual(新しいタブで開く) の The Set Builtin にある。
よくある原因
- 条件文脈:
if・while・untilの条件部分、&&と||の左側は、失敗を「偽」として使う場所なのでset -eの対象外である。 !による否定:! cmdは失敗を期待する書き方なので、そこも対象外になる。- 関数の巻き込み: 上の例のように、条件文脈で呼ばれた関数は本体の全コマンドが適用外になる。
関数の中にset -eを書き足しても変わらない。 - パイプライン: 既定ではパイプライン全体の終了ステータスは最後のコマンドのものだけである。
curl ... | tar xでcurlが失敗しても、tarが 0 を返せば通ってしまう。 local/exportとの同時代入:local x=$(false)はlocalコマンドの実行であり、その終了ステータスは 0 になる。
同じ行で書いた$(...)の失敗は消える。
単独のx=$(false)なら止まる。