シェルスクリプトでパイプ末尾以外のエラー対応
🔥

シェルスクリプトでパイプ末尾以外のエラー対応

2022/05/21に公開

シェルスクリプトでパイプを利用している時に末尾以外のエラーでも停止する必要がある場合の設定

set -o pipefail

set -o pipefail

未指定の場合、パイプの末尾のコマンドの実行結果が返却される。

未設定の場合

test.sh
# *.gzファイルがない場合
ls -1 *.gz | wc -l

RET=$?
if [ ${RET} != 0 ]; then
  exit 100
fi
exit 0
実行結果
$ bash -x test.sh
+ ls -1 '*.gz'
+ wc -l
ls: cannot access '*.gz': No such file or directory
0
+ RET=0
+ '[' 0 '!=' 0 ']'
+ exit 0

設定した場合

パイプの先頭の実行結果も返却されてくる。

test.sh
set -o pipefail
# *.gzファイルがない場合
ls -1 *.gz | wc -l

RET=$?
if [ ${RET} != 0 ]; then
  exit 100
fi
exit 0
実行結果
$ bash -x test.sh
+ set -o pipefail
+ ls -1 '*.gz'
+ wc -l
ls: cannot access '*.gz': No such file or directory
0
+ RET=2
+ '[' 2 '!=' 0 ']'
+ exit 100

実行例にあるようにwc -lの結果が出ているので実行はされる。

注意点

catやzcatをheadでパイプ切断すると141のエラーになるのでset -o pipefailを設定した場合には考慮が必要になる。

cat test.txt > /dev/null
echo $? # 0
cat test.txt | head -1> /dev/null
echo $? # 141
head -1 test.txt > /dev/null
echo $? # 0

zcatをheadで区切る場合は対処が必要になる。

関連:set -u

未定義変数に対する参照時に停止するようになる。
注意点は$1や$2なども対象であるので、次のように記載する必要がある。

P1=$1 # この場合はエラーで停止する
P1=${1:-} # デフォルト値を空での取得

関連:set -e

エラーがあったら停止する。
こちらは$?を判別して後続の処理を分けるような使い方をする場合は利用できない。
また副反応も多いので、このオプションは評価が分かれる。
たとえば次のようなコマンドでファイルが見つからない場合にも停止する。

ls -1 *.gz

このオプションは$?を判定するエラー処理を記載するようにして、あまり使用していない。

注意例

set -eをつけてもset -o pipefailをつけないとパイプの末尾の結果で、停止判定されている。
次のコードはwc -lもecho "TEST"も実行される

# *.gzファイルがない場合
set -e
ls -1 *.gz | wc -l
echo "TEST"

次のコードはwc -lまでは実行されるが、echo "TEST"は実行されない

# *.gzファイルがない場合
set -eo pipefail
ls -1 *.gz | wc -l
echo "TEST"

その他:個人的な方針

set -eは人間同士の紛争があったのであまり使っていない。

バッチとかテストで処理を並べて記載する等では利用するときがあるが、リポジトリに入れるシェルでは使わない。

論争になるときは手間でもデフォルトや標準のツールに従うことが多い。

Discussion