我打算将程序的输出通过管道传输到while read VAR
循环中,并break
在找到模式时将其输出,但事实并非如此。
概念证明:
inotifywait -qm -e create . | while read line; do echo $line; break; done
./ CREATE newfile
..
tail -f /var/log/syslog | while read line; do echo $line; break; done
Nov 6 22:44:05 section9 ntpdate[2381]: adjust time server 91.189.89.199 offset 0.272779 sec
无论源程序输出什么,它们都不会退出。预先设置set -x
表明循环永远不会迭代到第二个read
。$BASH_SUBSHELL
在这些示例中为 1。
tail
、等不应该inotifywait
收到SIGPIPE 并退出吗?
请注意,进程替换 ( while read ... break; done < <(tail -f ...)
) 可以正常工作。$BASH_SUBSHELL
在本例中为 0。
答案1
关键是,在 bash 下(其他 shell 可能有所不同),管道不会终止,直到所有命令管道中已完成。
为了理解,让我们考虑一下:
inotifywait -qm -e create . | while read line; do echo $line; break; done
当read
读取一行时,它会被回显,然后break
被执行,最后一个进程终止。然而,第一个过程将继续,直到写入标准输出失败。因此,循环将至少持续到inotifywait
尝试写入其第二输出线。由于缓冲的变化莫测,即使这样也可能不会发生。在发现发生之前可能需要几行。当尝试写入失败时,会发出 SIGPIPE。
现在,考虑另一种情况:
while read line; do echo $line; break; done < <(inotifywait -qm -e create .)
这里,没有管道。当break
执行时,while
循环完成。
文档
从“管道”部分man bash
:
贝壳等待所有命令在管道中终止......
这种行为是自己做出的选择bash
。它不是 POSIX 强制要求的。 POSIX状态:
如果管道不在后台(请参阅异步列表),则 shell 应等待管道中指定的最后一个命令完成,也可能会等待以便完成所有命令。
答案2
strace tail -f -n 5000 /var/log/messages |
while read line; do echo $line; break; done;
详细显示发生的情况:
[...]
write(1, "Nov 1 22:20:01 inno systemd[1]:"..., 4096) = 4096
即 tail 一次最多向管道写入 4096 个字节(这是管道可以缓冲的内容)。通常根本没有那么多数据tail
:
strace tail -f /var/log/messages | while read line; do echo $line; break; done;
节目
write(1, "Nov 7 07:00:02 inno systemd[1]:"..., 730) = 730
因此,tail
在将某些内容附加到文件之前,不会尝试再次写入。
我以为unbuffer
可以解决这个问题。我很惊讶事实并非如此。
unbuffer tail -f file1 | while read line; do echo $line; break; done