为什么从管道读取时“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 会读取并打印其余部分,但在第二种情况下则不然。

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

我还注意到它在 Ubuntu 16.04 和 Centos 6 中以这种方式工作,但在 Centos 7 中这两个命令都不会打印“zzz”。

答案1

当输入文件为可寻找的(就像从常规文件中读取)或不可寻找的(就像从管道中读取),sed(和其他标准实用程序)的行为会有所不同(阅读INPUT FILES中的部分这个链接)。

引用文档:

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

所以在:

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

sed在到达 EOF 之前执行了quit 命令,因此它在行首留下了文件偏移量zzz,因此cat可以继续打印剩余的行(GNU sed 在某些情况下不兼容 POSIX,请参见下文)。

并继续文档:

对于不可查找的文件,该文件的打开文件描述中的文件偏移量状态未指定

在这种情况下,行为是未指定的。大多数标准工具(包括)sed将尽可能多地消耗输入。它读取并传递该yyy行,并且q不恢复文件偏移量,因此没有留下任何内容cat


GNUsed不符合标准,取决于系统的 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必须一次读取一个字节。部分输出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
...

相关内容