Ansatzの備忘録

勉強したことあるいはふと思い立ったこと

シェルワンライナー160本ノック問題25

問題25 pipefail 時の困りごと

問題のファイルは

https://github.com/shellgei/shellgei160

からダウンロードできる。

スクリプト

#!/bin/bash -e
set -o pipefail
trap 'rm .tmp.top10' EXIT

sort | head > .tmp.top10

echo "+++++TOP 10+++++"
cat .tmp.top10

の5行目を変更してきちんと出力が得られるようにする問題だった。解説を読むまで全く何をすればいいのかわからなかった。

5行目が問題になる理由は、pipefail という設定をしている状況で sort コマンドが141というエラーを吐いて終了するためである。pipefail を設定していなければ sort コマンドのエラー自体は問題にならない。これを理解するのに手間取った。

シェルは、終了コード 0 で終了したコマンドは正常終了したとみなします。 終了コード 0 は成功を示します。 0 以外の終了コードは失敗を示します。 あるコマンドが致命的なシグナル N で終了したときには、 bash  は「128+N」の値を終了ステータスに使います。

とあることから、sort コマンドは141-128=13のシグナル、すなわちSIGPIPEで終了したとわかる。このシグナルはパイプにつながったコマンドが何かパイプに書き込もうとしたとき、パイプがなくなっていると発生するらしい。結局どこからこのシグナルが送られたのかはよくわからない。シェルが送っているのだろうか。それとも終了時にこのコマンドがほかのところへSIGPIPEを送っているのだろうか。

pipefail を使いつつSIGPIPEを防ぐには

sort | head > .tmp.top10 || true

と書く。こうすることでパイプの終了ステータスの値がORで使われるものとして扱われ、 sort | head > .tmp.top10 で処理が終わらずに続いていく。

pipefail の仕様は

設定されている場合、パイプラインの返り値は、 0 以外のステータスで終了した最後の (一番右の) コマンドの値になります。 パイプラインの全てのコマンドが成功の状態で終了すると 0  になります。 このオプションは、デフォルトで無効です。

となっている。改めて考えるとなぜ pipefail のもとで sort コマンドが終了したのかよくわからなくなってくる。pipefail が設定されてないときはSIGPIPEのシグナルがあってもコマンドは止まらないが、pipefail が設定されているときはSIGPIPEのシグナルでコマンドが止まる。どういう仕組みなのかがわからない。