パイプから読み取ると、「sed q」が異なる動作をするのはなぜですか?

パイプから読み取ると、「sed q」が異なる動作をするのはなぜですか?

次の内容で「test」というテストファイルを作成しました。

xxx
yyy
zzz

私は次のコマンドを実行しました。

(sed '/y/ q'; echo aaa; cat) < test

わかりました:

xxx
yyy
aaa
zzz

それから私は以下を実行しました。

cat test | (sed '/y/ q'; echo aaa; cat)

そして得る:

xxx
yyy
aaa

質問

sed「y」行が表示されるまで読み、印刷してから停止します。最初のケースでは、catは残りの内容を読み取って印刷しますが、2番目のケースではそうではありません。

これらの行動の違いの後に隠れている現象が何であるかを説明できる人はいますか?

また、Ubuntu 16.04とCentos 6ではこのように動作しますが、Centos 7では両方のコマンドが「zzz」を印刷しないことがわかりました。

答え1

入力ファイルが次のような場合閲覧可能(通常のファイルから読み込むような)または検索できません(パイプで読むのと似ています)sed(および他の標準ユーティリティ)は異なる動作をしますINPUT FILESこのリンク)。

引用文書:

標準ユーティリティが検索可能な入力ファイルを読み取り、ファイルの終わりに到達する前にエラーなしで終了する場合、ユーティリティは、開かれたファイル記述のファイルオフセットがユーティリティが処理した最後のバイトの後に正しく配置されていることを確認する必要があります。

だから:

(sed '/y/ q'; echo aaa; cat) < test

seduitコマンドはEOFに達する前に実行されるqため、行の先頭にファイルオフセットを残して残りの行を印刷することができます(GNU sedは場合によってはPOSIXと互換性がありません。以下を参照)zzzcat

そして、文書を続けると次のようになります。

検索できないファイルの場合、ファイルのオープンファイル記述にファイルオフセット状態は指定されません。

この場合、動作は指定されません。ほとんどの標準ツール(付属)はsedできるだけ多くの入力を消費します。yyy行を読み取って渡し、qファイルオフセットを復元しないので、何も残りませんcat


GNUはsed標準に準拠しておらず、システムのstdio実装とglibcのバージョンによって異なります。

$ (gsed '/y/ q'; echo aaa; cat) < test
xxx
yyy
aaa

ここの結果は、CEPHバックエンドを備えたOpenstack上で動作するMac OSX 10.11.6、仮想マシンCentos 7.2 - glibc 2.17、Ubuntu 14.04 - glibc 2.19から得られました。

これらのシステムでは、-uオプションを使用して標準動作を達成できます。

(gsed -u '/y/ q'; echo aaa; cat) </tmp/test

パイプの場合:

$ cat test | (gsed -u '/y/ q'; echo aaa; cat)
xxx
yyy
aaa
zzz

sed一度に1バイトずつ読み取る必要があるため、パフォーマンスは非常に非効率的です。部分出力strace:

$ strace -fe read sh -c '{ sed -u "/y/q"; echo aaa; cat; } <test'
...
[pid  5248] read(3, "", 4096)           = 0
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
xxx
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
yyy
...

関連情報