ShellScript
Bash

シェルスクリプトで "set -o pipefail" を付けるとSIGPIPEで死ぬ

はじめに

主にLinux環境で動くBashスクリプトについて書きます。

シェルスクリプトで、パイプを使ったコマンドを書く時に、安全弁として set -o pipefail を付けるのは良いマナーだと思います。
これにより、パイプの左側のコマンドが失敗したとき、スクリプトをそこで停止することができます。

pipefailの動作例
#!/usr/bin/env bash

set -eo pipefail

false | true
echo "won't be printed" # この行は実行されない

false コマンドは常に失敗するので、以降の行は実行されないというわけです。

SIGPIPEで死ぬパターン

しかし、ここで1つ落とし穴があります。
パイプの左側のコマンドが通常は成功する場合でも、パイプの右側のコマンドによって SIGPIPE が発生することがあるのです。

例えば、次のようなパターンです。

SIGPIPEで死ぬケース
# 例1
cat some-big-file | head -n 10

# 例2
cat some-big-file | grep -q foo

この場合も、スクリプトは停止し、以降の処理は実行されません。

どうやら、上のようにパイプの右側で headgrep -q コマンドを実行した際、パイプがクローズされてしまい、クローズされたパイプへの書込みが発生することで SIGPIPE が発生すると推測しています。

回避策

trapコマンドなどを使ってスマートに対処できないか試しましたが、残念ながら上手く行きませんでした。
ワークアラウンドなやり方になりますが、以下に挙げておきます。

①SIGPIPEが発生しないようにコマンドを書き換える

上の例1, 2の場合、次のように書き換えられます:

回避策①
# 例1改
tac some-big-file | tail -n 10

# 例2改
cat some-big-file | grep foo &>/dev/null

②コマンドがエラーにならないようにする

例えば次のようにします:

回避策②
cat some-big-file | head -n 10 || true

|| true の効果で、このワンライナーは絶対に失敗しなくなります。
|| : などでも良いですね。

set -o pipefail を止める

そもそも使わないという選択もあり得ますが、部分的に無効化することもできます。

回避策③
set +o pipefail # オプション無効化
cat some-big-file | head -n 10
set -o pipefail # オプション再有効化

まとめ

set -o pipefail を使う際はお気をつけ下さい。

参考