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


25

次を含む「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番目のケースではなく最初のケースでは、catは残りを読み取って出力します。

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

また、Ubuntu 16.04とCentos 6でもこのように動作することに気づきましたが、Centos 7ではコマンドは「zzz」を出力しません。


私の推測では、catstdinは実際のファイルにバインドされているため、最初のケースでは(サブシェルで)ファイル記述子を再利用できます。2番目のケースでは、stdinはパイプからのものであり、実際のファイルではありません。また(sed '/y/ q'; echo aaa; cat) < <(cat test)、印刷しないことに注意してくださいzzz
マーティンニョルト

1
より簡単な例:(head -n1; head -n1) < testおよびcat test | (head -n1; head -n1)
Martin Nyolt

回答:


22

入力ファイルがシーク可能(通常のファイルからの読み取りなど)またはシーク不可(パイプからの読み取りなど)の場合sed(およびその他の標準ユーティリティ)の動作は異なります(このリンクのINPUT FILESセクションを読む)。

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

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

だから:

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

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

そして、ドキュメントから継続:

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

この場合、動作は指定されていません。ほとんどの標準ツールinclude 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
...

1
GNUのsed場合、これはシステムのstdio実装に依存します。GNUシステム(GNU libcを使用)では、stdioによって管理されるファイルをlseekで戻すsedように、GNU は準拠exit()します。
ステファンシャゼラス

@StéphaneChazelas:確認方法は?私のCentOSに7.2、Ubuntuの14.04 VMは、sed準拠していない、私のmanjaroラップトップは、すべて同じ持っていないsed バージョン4.2.2
cuonglm

@StéphaneChazelas:フードの下で何かが起こったように聞こえます。私の仮想マシンでstrace -f sh -c '{ sed "/y/q"; echo aaa; cat; } <test'は、何lseek()も実行されていないことを示してください。一方、私のマンジャロでlseek()は前に呼び出されexit_group()ました
cuonglm

それはGNU libcのバージョンまでだと思います。main() { char buf[999]; gets(buf); }'プログラムでテストできます。
ステファンシャゼラス

1
@StéphaneChazelas:確認済み。私のVMには両方とも2.17と2.19がありますが、私のmanjaroのVMは2.23です。これはglibcのバグと考えられますか?glibcバージョン間の変更に関する情報はありますか
cuonglm
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.