Bash:无法跳出管道“while read”循环;流程替代工程

Bash:无法跳出管道“while read”循环;流程替代工程

我打算将程序的输出通过管道传输到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

相关内容