set -eだけでは止まらないパイプラインエラーとpipefailの必要性

  • Bash
  • Shell

この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。

シェルスクリプトの冒頭でよく見る set -euo pipefail。それぞれ何をしているか、なぜ3つセットで使われるかを整理する。

それぞれの意味

  • set -e:コマンドが非ゼロの終了ステータスを返した時点でスクリプトを即座に停止する
  • set -u:未定義(未設定)の変数を参照した時点で停止する
  • set -o pipefail:パイプライン中のどこかのコマンドが失敗したら、パイプライン全体を失敗扱いにする
  • set +e:- の代わりに + を付けると無効化になる。有効化が -、無効化が + という、多くのCLIツールとは逆の挙動なので覚えておくといい

順序に意味はなく、set -eu でも set -ue でも同じ。

set -e だけでは止まらないケース

set -e は「非ゼロ終了で必ず止まる」わけではない。以下のケースでは止まらない。

1. if / while / until の条件式の中

ターミナル
if grep foo file.txt; then
echo "found"
fi

grep が1(マッチなし)を返してもスクリプトは止まらない。条件式として使われた場合は set -e の対象外。

2. && や || の右左にあるコマンド

ターミナル
some_command || true

これは意図的に失敗を握りつぶす定番パターンだが、逆に言うと || や && の中にいるコマンドの失敗は set -e では拾われない。

3. パイプラインの最後以外のコマンド

ターミナル
false | grep foo

パイプラインの終了ステータスは、デフォルトでは最後のコマンド(この場合 grep)のものしか見ない。false がパイプの前段で失敗していても、grep が成功していればパイプライン全体は成功扱いになり、set -e は反応しない。

pipefail で3番目のケースを拾う

ターミナル
set -euo pipefail
false | grep foo # ここで停止する
echo "ここには来ない"

pipefail を有効にすると、パイプライン中のどれか一つでも失敗すればパイプライン全体が失敗扱いになる。これと -e が組み合わさって、パイプの途中の失敗も検知できるようになる。

注意点

  • set -o pipefail は bash / zsh の機能で、POSIX shには存在しない。シェバンが #!/bin/sh だと動かない(dashなど一部シェルはエラーになる)ので、使うなら #!/bin/bash にする
  • pipefail を有効にしても、意図的に失敗を無視したいパイプがあるなら個別に || true を付ける必要がある

実務での形

#!/bin/bash
set -euo pipefail
# 一時的に緩めたい場合
set +e
some_command_that_may_fail
set -e

このセットが「シェルスクリプトの事故防止」としては現状のデファクトスタンダードになっている。