パイプ途中のコマンドの終了コードを取得したい

set -o pipefailを使うと、パイプ途中のコマンドが失敗した場合にスクリプトを停止できる。 ただし、どのコマンドが失敗したのかを特定したり、失敗を許容しつつ終了コードだけ取得したい場合には使えない。

$ cat nofile.txt | grep ERROR | wc -l
cat: nofile.txt: No such file or directory
0
$ echo $?
0

このとき、catの終了コードもgrepの終了コードも$?では取得できない。

PIPESTATUSを使う

bashにはPIPESTATUSという配列変数があり、直前に実行したパイプの各コマンドの終了コードが格納される。

$ cat nofile.txt | grep ERROR | wc -l
cat: nofile.txt: No such file or directory
0
$ echo "${PIPESTATUS[@]}"
1 1 0

インデックスで個別のコマンドの終了コードを取得できる。

$ echo "${PIPESTATUS[0]}"  # cat の終了コード
1
$ echo "${PIPESTATUS[1]}"  # grep の終了コード
1
$ echo "${PIPESTATUS[2]}"  # wc の終了コード
0

スクリプトでの使い方

PIPESTATUSは次のコマンドを実行すると上書きされる。 コマンド実行直後に別の配列変数にコピーして使う。

#!/bin/bash

cat /var/log/app.log | grep ERROR | wc -l
statuses=("${PIPESTATUS[@]}")

if [[ ${statuses[0]} -ne 0 ]]; then
    echo "ログファイルの読み込みに失敗しました" >&2
    exit 1
fi

if [[ ${statuses[1]} -ne 0 && ${statuses[1]} -ne 1 ]]; then
    echo "grepの実行に失敗しました" >&2
    exit 1
fi

grepは検索にマッチしない場合も終了コード1を返す。 終了コードが1の場合はマッチなしとして扱い、2以上をエラーとして扱う。

grepの終了コードの詳細はgrepコマンドで終了ステータスコードだけほしいときに検索結果を標準出力に出さない方法 を参照。

set -o pipefailとの使い分け

set -o pipefailはパイプ全体の失敗を$?で検知するためのオプション。 どのコマンドが失敗したかの特定や、失敗の種類に応じた処理の分岐が必要な場合はPIPESTATUSを使う。

用途手段
パイプ途中の失敗でスクリプトを停止するset -o pipefail
どのコマンドが失敗したか特定するPIPESTATUS
失敗の種類に応じて処理を分岐するPIPESTATUS

関連

参考