web-dev-qa-db-ja.com

パイプから読み取るときに「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'を含む行に出会うまで読み取りと出力を行い、その後停止します。最初のケースでは、2番目のケースではなく、猫は残りを読み取り、出力します。

誰かがこの行動の違いの背後にある現象を説明できますか?

また、Ubuntu 16.04とCentos 6ではこのように機能しますが、Centos 7ではどちらのコマンドも「zzz」を出力しません。

25
Antti Kuusela

入力ファイルがseekable(通常のファイルからの読み取りと同様)またはun-seekable(パイプからの読み取りと同様)、sed(およびその他の標準ユーティリティ)の動作は異なります(_ このリンクINPUT FILESセクションを読み取ります)。

ドキュメントからの引用:

標準のユーティリティがシーク可能な入力ファイルを読み取り、ファイルの終わりに達する前にエラーなしで終了した場合、ユーティリティは、開いているファイルの説明のファイルオフセットが、ユーティリティによって処理された最後のバイトのすぐ後に正しく配置されるようにします。

だから:

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

sedはEOFに到達する前にquitコマンドを実行したため、zzz行の先頭にファイルオフセットが残っているため、catは残りの行の印刷を続行できます(GNU sed一部の条件ではPOSIXに準拠していません。以下を参照してください)。

そして、ドキュメントから続けます:

シークできないファイルの場合、そのファイルの開いているファイルの説明におけるファイルオフセットの状態は指定されていません

この場合の動作は規定されていません。 sedを含むほとんどの標準ツールは、入力を可能な限り使用します。ファイルオフセットを復元せずにyyy行とquitを読み取って渡すため、catには何も残りません。


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

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

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

これらのシステムでは、-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
...
22
cuonglm