set -eu
VAR=$(zcat file.gz | head -n 12)
正常に動作します
set -eu -o pipefail
VAR=$(zcat file.gz | head -n 12)
bash が失敗して終了します。これはどのようにしてパイプ障害を引き起こすのでしょうか?
file.gz には数百万行 (約 750 MB、圧縮) が含まれていることに注意してください。
ベストアンサー1
エラーの原因となる一連のイベントは次のとおりです。
- いずれかのコンポーネントが失敗した場合、パイプライン全体が失敗したとみなすようにシェルに指示します。
zcat
出力を に書き込むように指示していますhead
。head
次に、 12 行よりはるかに長い入力ストリームから 12 行を読み取った後に終了するように指示します。
つまり、zcat
宛先パイプラインが早期に閉じられ、入力ファイルの解凍バージョンを正常に書き込むことができませんでした。これは、何らかのエラーが発生したことによるユーザーの意図によるものであるかどうかを知る方法はありません。
ディスクへの書き込みに使用していて容量が不足した場合、またはネットワーク ストリームへの書き込みに使用していzcat
て接続が失われた場合、失敗を示すステータスで終了するのはまったく正しく適切です。これは単にそのルールの別のケースです。
zcat
オペレーティング システムによって発生している特定のエラーはEPIPE
、write
次の条件下で syscall によって返されます。どのプロセスでも読み取り用に開かれていないパイプに書き込もうとしました。
(このFIFOの唯一のリーダー)が終了した後head
、パイプラインの入力側への書き込みに対してないEPIPEを返すのはバグです。zcat
出力の書き込みエラーを黙って無視し、このイベントを反映する終了ステータスなしで不正確な出力ストリームを生成することができるのは、同じくバグである。
ちなみに、シェル オプションを一切変更したくない場合は、プロセス置換を使用するという回避策を検討することもできます。
var=$(head -n 12 < <(zcat file.gz))
この場合、 はzcat
パイプライン コンポーネントではないため、成功の判定には終了ステータスは考慮されません。($var
成功/失敗を独立して判定したい場合は、 が 12 行であるかどうかをテストしてください)。
より包括的なソリューションは、ネイティブの gzip サポートを備えた Python インタープリターを導入することで実装できます。シェル スクリプトに埋め込まれたネイティブ Python 実装 (Python 2 と 3.x の両方と互換性があります) は次のようになります。
zhead_py=$(cat <<'EOF'
import sys, gzip
gzf = gzip.GzipFile(sys.argv[1], 'rb')
outFile = sys.stdout.buffer if hasattr(sys.stdout, 'buffer') else sys.stdout
numLines = 0
maxLines = int(sys.argv[2])
for line in gzf:
if numLines >= maxLines:
sys.exit(0)
outFile.write(line)
numLines += 1
EOF
)
zhead() { python -c "$zhead_py" "$@"; }
...これzhead
により、入力データが不足しても失敗しないが、する本物の I/O 障害またはその他の予期しないイベントの場合は、失敗終了ステータスを渡します。(使用法は の形式でありzhead in.gz 5
、 から 5 行を読み取りますin.gz
)。