鉴于这个最小的例子
( 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
直到一秒钟过去才有输出,并且两条线都立即出现在输出上,这是我没想到的。
为什么会发生这种情况,如何使最后一个版本的行为与前三个命令相同?
当 (至少 GNU)
grep
的输出不是终端时,它会缓冲其输出,这就是导致您看到的行为的原因。您可以使用 GNUgrep
的--line-buffered
选项禁用此功能:或
stdbuf
实用程序:关闭管道中的缓冲有更多关于这个主题的内容。
简化解释
像许多实用程序一样,这不是一个程序所特有的,它在行缓冲和完全缓冲
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
不改变程序内部结构而只是插入一个伪终端作为标准输出的工具,以便决策以“交互式”的形式出现,以影响这个。进一步阅读
利用
使 grep 一次不缓冲超过一行。