从管道读取时,为什么“ 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'的行,然后停止。在第一种情况下,而不是第二种情况下,cat读取并打印其余部分。

有人可以解释这种行为差异背后的现象是什么吗?

我也注意到它在Ubuntu 16.04和Centos 6中是这样工作的,但是在Centos 7中,这两个命令都不会输出'zzz'。


我的猜测是,cat(在子外壳中)在第一种情况下可以重用文件描述符,因为stdin绑定到实际文件。在第二种情况下,stdin来自管道,而不是真实文件。请注意,也(sed '/y/ q'; echo aaa; cat) < <(cat test)不会打印zzz
马丁·尼约尔特

1
一个简单的例子:(head -n1; head -n1) < testcat test | (head -n1; head -n1)
马丁Nyolt

Answers:


22

当输入文件是可查找的(例如从常规文件中读取)或不可查找的(例如从管道中读取)时,sed(和其他标准实用程序)的行为将有所不同(此链接中的“阅读” INPUT FILES部分)。

从文档引用:

当标准实用程序读取可搜索的输入文件并在到达文件结尾之前无错误终止时,该实用程序应确保打开的文件描述中的文件偏移量恰好位于该实用程序处理的最后一个字节之后。

所以在:

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

sedq在到达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上运行。

在这些系统上,可以使用-uoption来实现标准行为:

(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
...

1
对于GNU sed,这取决于系统的stdio实现。在GNU系统(带有GNU libc)上,GNU sed将会兼容,同时exit()还会回溯由stdio管理的文件。
斯特凡Chazelas

@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()执行任何操作,而在我的manjaro中lseek(),之前调用了a exit_group()
cuonglm

我想这取决于GNU libc的版本。您可以使用main() { char buf[999]; gets(buf); }'程序进行测试。
斯特凡Chazelas

1
@StéphaneChazelas:确认。我的两个VM都有2.17和2.19,而我的manjaro中的是2.23。这是否是glibc错误?您是否有任何有关glibc版本之间的更改的信息
cuonglm '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.