尾部日志上有多个管道 - 最后一个管道永远不会接收标准输入

尾部日志上有多个管道 - 最后一个管道永远不会接收标准输入

我在 Ubuntu 上遇到了一个有趣的状态。以下步骤对此进行了最好的描述。

只需一根管道,我就能看到我期望的结果

# In shell A
tail -f foo.log | grep aaa

# In shell B
echo aaa >> foo.log

# Shell A prints out `aaa`

但由于有多个管道,我什么也看不见

# In shell A
tail -f foo.log | grep aaa | grep bbb

# In shell B
echo aaa bbb >> foo.log

# Nothing ever prints in shell A

但如果我只是回应的话,它就可以正常工作。

echo 'aaa bbb' | grep aaa | grep bbb

这是我尝试创建最小复现的尝试——我最初在尝试从 adb logcat(Android 开发工具)发送日志时遇到了这个问题。我也尝试过在 zsh、bash 和 fish 中。

我认为这与我的 inotify 观察者限制有关,但是增加它并没有改变任何事情。

答案1

这是因为管道中的缓冲,通常它不关心线路并且可以积累数据。

我认为tail -f它本身使用行缓冲;并且最后grep写入 tty,因此它也使用行缓冲。因此您的第一个示例有效。

grep中间部分有所不同,您需要通过强制行缓冲或禁用缓冲来调整其行为。下面的命令将按预期工作。

  • 如果您grep支持--line-buffered(在 Ubuntu 中支持):

      tail -f foo.log | grep --line-buffered aaa | grep bbb
    
  • 更通用的解决方案(它们可以与除 之外的许多其他过滤器一起使用grep):

      tail -f foo.log | unbuffer -p grep aaa | grep bbb
      tail -f foo.log | stdbuf -oL grep aaa | grep bbb
      tail -f foo.log | stdbuf -o0 grep aaa | grep bbb
    

请参阅man 1 grepman 1 unbufferman 1 stdbuf以了解详细信息和怪癖。

笔记:

  • 这两种解决方案都不可移植(grep --line-bufferedunbuffer并且stdbuf未被 POSIX 指定)。
  • 如果你能做到这一点,grep --line-buffered那么这应该是你的选择。使用额外的工具是没有意义的。
  • 有关 Unix 和 Linux SE 的相关问题:关闭管道缓冲
  • unbufferstdbuf 以完全不同的方式工作
  • 在这里stdbuf,行缓冲 ( -oL) 应该优于无缓冲 ( -o0),因为
    • 它很可能表现更好,
    • 并且管道的其他部分无论如何都使用线路缓冲。
  • 如果您的最后一个grep文件写入另一个文件,它的行为将与其他文件一样grep。在这种情况下,如果您希望行立即出现在最终文件中,那么您还应该修改最后一个文件的行为grep
  • 在 中fish,如果grep是包装函数,则使用 可能无法获得所需的行为--line-buffered。请使用command grep --line-buffered。请参阅此问题:输出管道等待 EOFfish

附注:(tail foo.log | grep aaa | grep bbbtail没有-f)不会导致问题,因为tail退出。退出时tail,第一个grep检测到 EOF,刷新其缓冲区并退出,然后第二个grep执行相同操作。

相关内容