パイプ途中のコマンドの終了コードを取得したい
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 |
関連
- パイプ全体の失敗を検知するにはset -euo pipefailでエラーに強いシェルスクリプトを書く を参照
- エラー発生時の後処理はtrapコマンドによる後処理の設定 を参照
参考
\第一線のプログラマーの行動原理を学べる!/
