我在 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 grep
、man 1 unbuffer
和man 1 stdbuf
以了解详细信息和怪癖。
笔记:
- 这两种解决方案都不可移植(
grep --line-buffered
,unbuffer
并且stdbuf
未被 POSIX 指定)。 - 如果你能做到这一点,
grep --line-buffered
那么这应该是你的选择。使用额外的工具是没有意义的。 - 有关 Unix 和 Linux SE 的相关问题:关闭管道缓冲。
unbuffer
和stdbuf
以完全不同的方式工作。- 在这里
stdbuf
,行缓冲 (-oL
) 应该优于无缓冲 (-o0
),因为- 它很可能表现更好,
- 并且管道的其他部分无论如何都使用线路缓冲。
- 如果您的最后一个
grep
文件写入另一个文件,它的行为将与其他文件一样grep
。在这种情况下,如果您希望行立即出现在最终文件中,那么您还应该修改最后一个文件的行为grep
。 - 在 中
fish
,如果grep
是包装函数,则使用 可能无法获得所需的行为--line-buffered
。请使用command grep --line-buffered
。请参阅此问题:输出管道等待 EOFfish
。
附注:(tail foo.log | grep aaa | grep bbb
即tail
没有-f
)不会导致问题,因为tail
退出。退出时tail
,第一个grep
检测到 EOF,刷新其缓冲区并退出,然后第二个grep
执行相同操作。