鉴于这个最小的例子
( 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
直到一秒钟后才会有输出,并且两行都立即出现在输出上,这我没有期待。
为什么会发生这种情况?如何使最后一个版本的行为与前三个命令相同?
答案1
当(至少 GNU)grep
的输出不是终端时,它会缓冲其输出,这就是导致您所看到的行为的原因。您可以使用 GNUgrep
的选项禁用此--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
关闭管道中的缓冲关于这个主题有更多内容。
答案2
简单说明
与许多实用程序一样,这并不是某个程序特有的东西,grep
它的标准输出在行缓冲和全缓冲。在前一种情况下,C 库将输出数据缓冲在内存中,直到保存这些数据的缓冲区被填满或添加换行符(或者程序干净地结束),然后它调用write()
实际写入缓冲区内容。在后一种情况下,只有内存缓冲区变满(或程序干净地结束)才会触发write()
.
更详细的解释
这是众所周知的,但稍微有点错误的解释。事实上,标准输出不是行缓冲的,而是智能缓冲在 GNU C 库和 BSD C 库中。标准输出是还读标准时脸红输入排气它是内存缓冲区(预读输入)和 C 库必须调用read()
以获取更多输入和它正在读取新行的开头。 (这样做的一个原因是为了防止当另一个程序将自身连接到过滤器的两端并希望能够逐行操作,在写入过滤器和读取过滤器之间交替时发生死锁;就像 GNU 中的“协进程”一样awk
例如。)
C 库的影响
grep
其他实用程序根据它们检测到的标准输出来执行此操作,或者更严格地说,它们使用的 C 库执行此操作,因为这是 C 语言编程的定义功能。当(且仅当)它不是交互式设备时,他们选择完全缓冲,否则他们选择智能缓冲。管道被认为不是交互式设备,因为至少在 Unix 和 Linux 世界中,交互式设备的定义本质上是isatty()
对相关文件描述符返回 true 的调用。
禁用完全缓冲的解决方法
有些实用程序grep
有特殊的选项,例如--line-buffered
改变此决定的选项,正如您所看到的,它的名称是错误的。但实际上,人们可以使用的过滤程序中只有极少数具有这样的选项。
更一般地说,可以使用深入 C 库的特定内部结构并更改其决策的工具(如果要更改的程序是 set-UID,则存在安全问题,并且还特定于特定的 C 库,而且确实是)特定于用 C 语言编写或分层于 C 语言之上的程序),或诸如此类的ptybandage
工具不要更改程序的内部结构,但只需插入一个伪终端作为标准输出,以便决策以“交互式”形式出现,从而影响这一点。
进一步阅读
答案3
使用
grep --line-buffered
使 grep 一次不会缓冲多于一行。