当输入文件是可查找的(例如从常规文件中读取)或不可查找的(例如从管道中读取)时,sed
(和其他标准实用程序)的行为将有所不同(此链接中的“阅读” INPUT FILES
部分)。
从文档引用:
当标准实用程序读取可搜索的输入文件并在到达文件结尾之前无错误终止时,该实用程序应确保打开的文件描述中的文件偏移量恰好位于该实用程序处理的最后一个字节之后。
所以在:
(sed '/y/ q'; echo aaa; cat) < test
sed
q
在到达EOF之前执行uit命令,因此它在文件zzz
行的开头留有文件偏移,因此cat
可以继续打印其余行(在某些情况下GNU sed不兼容POSIX,请参见下文)。
并从文档继续:
对于不可搜索的文件,该文件的打开文件描述中的文件偏移状态未指定
在这种情况下,行为是不确定的。大多数标准工具,包括sed
将尽可能多地消耗输入。它读取了该yyy
行的内容,并在q
不恢复文件偏移的情况下进行了读取,因此无需再进行任何操作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
option来实现标准行为:
(gsed -u '/y/ q'; echo aaa; cat) </tmp/test
对于管道:
$ cat test | (gsed -u '/y/ q'; echo aaa; cat)
xxx
yyy
aaa
zzz
由于sed
必须一次读取一个字节,因此导致性能极低下。来自的部分输出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
...
cat
(在子外壳中)在第一种情况下可以重用文件描述符,因为stdin绑定到实际文件。在第二种情况下,stdin来自管道,而不是真实文件。请注意,也(sed '/y/ q'; echo aaa; cat) < <(cat test)
不会打印zzz
。