如果通过cat管道传输,则grep直到EOF才输出


19

给出这个最小的例子

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; )

它输出LINE 1,然后,在一秒钟后,输出LINE 2如预期


如果我们通过管道 grep LINE

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE

行为与预期的情况相同。


如果,或者,我们将其通过管道传输到 cat

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | cat

行为再次与预期相同。


然而,如果我们管grep LINE,然后cat

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE | cat

在经过一秒钟之前没有任何输出,并且这两行立即出现在输出中,这是我没想到的


为什么会发生这种情况,如何使最新版本的行为与前三个命令相同?


cat连接文件。您想通过管道连接做cat什么?
道格拉斯(Douglas)在

15
@DouglasHeld在不带参数的情况下,cat只需将其读取stdin并输出到中stdout。当然,我想出了这个问题,用echoand 代替了很多复杂的东西cat,但是事实证明它们是无关紧要的,因为这个问题以更简单的例子显示出来。
lisyarus

3
@DouglasHeld:给cat配管通常会迫使stdout不再是终端。例如,这是获得许多不使用彩色输出的命令的简便方法。
wchargin

我发誓这是关于Stack Overflow 的另一个问题的重复!
iBug

@wchargin非常感谢,您教给我一些关于posix的新知识,而我从未知道。
道格拉斯

Answers:


38

当(至少GNU)grep的输出不是终端时,它将缓冲其输出,这就是导致您所看到的行为的原因。您可以使用GNU grep--line-buffered选项禁用此功能:

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep --line-buffered LINE | cat

stdbuf实用程序:

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | stdbuf -oL grep LINE | cat

在管道中关闭缓冲具有更多有关此主题的信息。


26

简化说明

像许多实用程序一样,这不是一个程序所特有的,grep在行缓冲完全缓冲之间改变其标准输出。在前一种情况下,C库将输出数据缓冲在内存中,直到填充了保存这些数据的缓冲区或向其中添加了换行符(或程序干净结束),然后它调用write()以实际写入缓冲区内容。在后一种情况下,只有内存缓冲区已满(或程序干净地结束)才会触发write()

更详细的解释

这是众所周知但略有错误的解释。实际上,在GNU C库和BSD C库中,标准输出不是行缓冲的,而是智能缓冲的。当读取标准输入耗尽(预读输入的)内存缓冲区时,标准输出也将刷新,并且C库必须调用以获取更多输入并且正在读取新行的开头。(这样做的一个原因是为了防止当另一个程序将自身连接到过滤器的两端并希望能够逐行操作,在写入过滤器和从过滤器读取之间进行交替操作时发生死锁;就像GNU中的“协同处理” 例如。)read()awk

C库影响

grep其他实用程序(或更严格地说,它们使用的C库)执行此操作,因为这是C语言编程的定义功能-基于它们检测到的标准输出是什么。如果(且仅)当它不是交互式设备时,他们选择完全缓冲,否则他们选择智能缓冲。管道被认为不是交互式设备,因为至少在Unix和Linux中,交互式设备的定义实质上是isatty()对相关文件描述符返回true 的调用。

禁用完全缓冲的解决方法

某些实用程序grep具有特殊的选项,例如--line-buffered更改该决定的选项,如您所见,它的名称是错误的。但是实际上,可以使用的过滤器程序中只有很少一部分具有这种选择。

更一般而言,人们可以使用一些工具,这些工具可以深入研究C库的特定内部结构并更改其决策(如果要更改的程序是set-UID,则存在安全问题,并且还特定于特定的C库,实际上是特定于用C语言编写或在C语言之上编写的程序),或诸如此类的工具ptybandage它们不会更改程序的内部结构,而只是插入一个伪终端作为标准输出,以使该决定为“交互”,影响这一点。

进一步阅读


1
如果短语“行缓冲”是用词不当,那么这实际上不是grep底层库调用setbuf/setvbuf的错。我不知道有关C标准的可靠在线参考资料,但例如Linux和FreeBSD手册页以及POSIX描述setvbuf称为“行缓冲”。甚至它的符号常量也是_IOLBF
ilkkachu

好了,现在您学得更好了。这种缓冲策略在GNU C库DOCO描述,尽管是短暂的。 洛朗·贝科特(Laurent Bercot)在这件事上更为直率。 我也提到过。
JdeBP '18

对于这种出色的输出缓冲解释,我认为“您的期望是错误的”并不是一个很好的标题。希望您不要介意我将其删除,并为答案的每个部分添加了一些描述性标题。
安东尼·G-莫妮卡的正义'18

2
@ilkkachu C标准确实使用“行缓冲”。根据7.21.3文件第3段:“当流没有缓冲时,...当流完全缓冲时,...当流进行行缓冲时,字符应作为主机与主机环境之间的来回传输。实际上,C标准使用精确的短语“行缓冲”五次。因此,这不是误称。
安德鲁·亨利

1
此外,据我所知,此处描述为“智能缓冲”的方法似乎正是C标准所描述的“行缓冲”。具体来说,除了在换行符处刷新缓冲区外,“当对行进行行缓冲时,当在未缓冲的流上请求输入时,或当在需要从主机环境传输字符的行缓冲流上请求输入。” 因此,这不是GNU或BSD的怪癖,而是该语言的要求。
John Bollinger

7

使用

grep --line-buffered

使grep一次不能缓冲多于一行。

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.