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

ari*_*lCo 7 bash pipe

我打算将程序的输出通过管道传输到while read VAR循环中,并break在找到模式时,但事实并非如此。

概念证明:

inotifywait -qm -e create . | while read line; do echo $line; break; done
./ CREATE newfile
Run Code Online (Sandbox Code Playgroud)

..

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
Run Code Online (Sandbox Code Playgroud)

无论源程序输出什么,这些都永远不会退出。set -x预先设置表明循环永远不会迭代到第二个read。$BASH_SUBSHELL在这些示例中为 1。

tail、inotifywait、 等不应该接收 SIGPIPE 并退出吗?

请注意,进程替换 ( while read ... break; done < <(tail -f ...)) 工作正常。$BASH_SUBSHELL在这种情况下为 0。

Joh*_*024 13

关键是,在 bash 下(其他 shell 可能有所不同),直到管道中的所有命令都完成后,管道才会终止。

为了理解,让我们考虑:

inotifywait -qm -e create . | while read line; do echo $line; break; done
Run Code Online (Sandbox Code Playgroud)

当read读取一行时,它被回显,然后break被执行,最后一个进程终止。但是,第一个过程会继续,直到写入 stdout 失败。因此,循环将至少持续到inotifywait尝试写入其第二行输出。由于缓冲的变化无常,即使那样也可能不会发生。在发现发生之前可能需要几行。当尝试写入失败时,会发出 SIGPIPE。

现在,考虑另一种情况:

while read line; do echo $line; break; done < <(inotifywait -qm -e create .)
Run Code Online (Sandbox Code Playgroud)

在这里,没有管道。当break被执行时,while循环完成。

文档

从“管道”部分man bash:

shell等待管道中的所有命令终止......

这种行为是由 做出的选择bash。它不是 POSIX 强制要求的。 POSIX指出:

如果管道不在后台(参见异步列表),shell 将等待管道中指定的最后一个命令完成,也可能等待所有命令完成。