解説
途中で失敗したのに成功になる?Bashのパイプラインとpipefailを知ろう
false | catの終了状態をBashで比較。pipefailが変える値、複数の失敗での扱い、処理を自動停止する設定との違いを確認する。

二つのコマンドを|でつないだら、前のコマンドが失敗したのに成功として扱われた。Bashのパイプラインは、初期状態では最後のコマンドの終了状態を使うからだよ。
今回はファイルを書き換えず、falseとcatを使って小さく確かめる。Bashが必要な例で、どのshでも同じ設定が使えるという説明ではないよ。
まず、最後のコマンドの値を見る
bash --noprofile --norc -c 'false | cat'
printf 'exit=%s\n' "$?"
falseは失敗を表す終了状態1を返し、catは入力を読み終えて成功の0を返す。この例で表示されるのはexit=0。前半の失敗を見ていないことになるね。
|は標準出力を次へ渡す仕組みで、前の処理が成功したことまで自動的に保証するわけではない。何も表示されなかったことも、成功の証拠にはならないよ。
pipefailを有効にすると変わる
bash --noprofile --norc -o pipefail -c 'false | cat'
printf 'exit=%s\n' "$?"
今度はexit=1になる。pipefailが有効だと、パイプラインに失敗したコマンドがある場合、その情報が終了状態へ反映される。
設定は呼び出した子のBashへ渡している。この練習のために、普段使っているシェルの設定ファイルを編集する必要はないよ。
複数が失敗したら、右側の失敗の値
bash --noprofile --norc -o pipefail -c '(exit 3) | (exit 7)'
printf 'exit=%s\n' "$?"
結果はexit=7。pipefailの値は、最後、つまり右側で最も後ろにある非0の終了状態になる。「最初に失敗した処理の番号」と覚えないようにしよう。
すべて成功なら0になる。どの処理が失敗したかを詳しく調べたい場合は、各コマンドの状態やログを別に確認する必要があるね。
失敗の値と、自動的な停止は別
pipefailはパイプラインの終了状態を変える設定。それだけで、後ろに書いたすべての処理が自動的に中止されるわけではないよ。
スクリプトでどう対処するかは、ifで成功・失敗を分けるなど、必要な制御を設計する。set -eにも適用条件や例外があるため、「失敗したら必ず止まる」一つの万能な指定として扱わないようにしよう。
$?は、次のコマンドで変わる
終了状態を見る$?は、直前の処理の値。確認前に別のコマンドを動かすと、その処理の終了状態へ変わる。
上の例でもprintfが終わったあとの$?は、printfの値になる。後から保存しておきたい場合は、対象の直後に変数へ取るなど、見る時点を揃える必要があるよ。
編集部ではBashで、初期状態の0、pipefail有効時の1、二つの失敗の7を確認した。終了コードの基本、パイプとリダイレクトも合わせて読むと整理しやすいよ。

みんなのコメント 0
感想や、試してみたことを聞かせてね。コメント・返信は承認後に公開されます。
最初のコメントをどうぞ。