stdout 是否可以被两个异步运行的 bourne(或 bash,如果重要的话)子 shell 命令覆盖?
(tail -f ./file1 & tail -f ./file2) | cat
我不关心行顺序,只是每个输出行都由一个输入行组成。我担心某些行可能会被部分覆盖或交错。
我通过运行四个命令进行了测试,每个命令输出一个唯一的行 1500 万次。它似乎有效,但我有点预计它会失败。
有人能解释一下这如何不会破坏吗?每个子 shell 是否都被缓冲并且一次只有一个子 shell 可以写入标准输出?或者这是如何管理的。
有一个更好的方法吗?
(别介意我是使用tail
在上面的子shell中说明性目的。我实际上想运行另外两个命令,一次连续输出一行到标准输出。)
答案1
贝壳在那里几乎没有参与。他们所做的就是创建管道并启动这 3 个命令,然后这些命令独立于 shell 并行运行。
这里重要的是两个 tail 命令都将文件描述符写入同一管道的同一写入端。
如果你这样做:
printf foo1 >> file1; sleep 1
printf foo2 >> file2; sleep 1
printf 'bar1\n' >> file1; sleep 1
printf 'bar2\n' >> file2
你会看到的:
foo1foo2bar1
bar2
因为那些都是这么写的。您需要确保您的命令输出一个满的一次写入一行,并且这些行小于 PIPE_BUF(Linux 上为 4096 字节),以保证 write() 是原子的(它也可以一次写入多个整行,前提是它们都已满并且它们的累积大小小于 PIPE_BUF)。
使用 GNU grep
,您可以通过将命令传递到grep --line-buffered '^'
(tail -f ./file1 | grep --line-buffered '^' &
tail -f ./file2 | grep --line-buffered '^') | cat
这将保证两个命令的输出的每一行都有一个write()
系统调用(在命令不终止其最后一行输出的情况下,grep
将添加缺少的换行符)
答案2
1. 糟糕的解决方案
在默认配置中,stderr 是不是缓冲,但标准输出是。
因此,您的问题最简单的解决方案是
- 提供所使用的工具逐行编写所有内容
- 将它们的输出重定向到 stderr (
>&2
)
然而,它不起作用,因为这种缓冲发生在进程内部的 C 库中。如果将它们的标准输出重定向到标准错误,则该标准错误也将是无缓冲的。
2.更好的解决方案
将它们的输出通过管道传输到读取所有内容的进程中,然后按行将它们写出。最简单的方法是
tool1 | while read; do echo "$REPLY"; done & tool2 | while read; do echo "$REPLY"; done
对于多个命令/脚本的“美丽”并行执行,这里你可以阅读我的另一个答案。
3.真正的解决方案
不幸的是,进程输出主要由 libc 缓冲,即它们的内部事物,它们如何将输出映射到write(1, ...)
内核级别的调用。这取决于他们。如果不改变它们,我们就无法控制它们的输出,即它们尚未编写的内容。