はじめに
主にLinux環境で動くBashスクリプトについて書きます。
シェルスクリプトで、パイプを使ったコマンドを書く時に、安全弁として set -o pipefail
を付けるのは良いマナーだと思います。
これにより、パイプの左側のコマンドが失敗したとき、スクリプトをそこで停止することができます。
#!/usr/bin/env bash
set -eo pipefail
false | true
echo "won't be printed" # この行は実行されない
false
コマンドは常に失敗するので、以降の行は実行されないというわけです。
SIGPIPEで死ぬパターン
しかし、ここで1つ落とし穴があります。
パイプの左側のコマンドが通常は成功する場合でも、パイプの右側のコマンドによって SIGPIPE
が発生することがあるのです。
例えば、次のようなパターンです。
# 例1
cat some-big-file | head -n 10
# 例2
cat some-big-file | grep -q foo
この場合も、スクリプトは停止し、以降の処理は実行されません。
どうやら、上のようにパイプの右側で head
や grep -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
を使う際はお気をつけ下さい。
参考
- Man page of BASH ... trapコマンドのリファレンス
- Man page of SIGNAL
- linux - Why exit code 141 with grep -q? - Stack Overflow ... この問題を端的に表しているQ&A
- Don't fear SIGPIPE! ... GNU coreutilsのメンテナの (at)pixelb がシェルスクリプト等におけるSIGPIPEの取り扱いについて書いています。どうもシェルスクリプトで完璧にやるには泥臭く終了ステータスを判定するしかないのかな、という印象ですが…。
- 第6回 UNIXプログラミングの勘所(3):Perl Hackers Hub|gihyo.jp … 技術評論社 ... ネットワークプログラミングにおけるSIGPIPEの問題と取り扱い方について