两个异步子 shell 命令可以安全地写入共享的标准输出吗?

两个异步子 shell 命令可以安全地写入共享的标准输出吗?

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 是不是缓冲,但标准输出是。

因此,您的问题最简单的解决方案是

  1. 提供所使用的工具逐行编写所有内容
  2. 将它们的输出重定向到 stderr ( >&2)

然而,它不起作用,因为这种缓冲发生在进程内部的 C 库中。如果将它们的标准输出重定向到标准错误,则该标准错误也将是无缓冲的。

2.更好的解决方案

将它们的输出通过管道传输到读取所有内容的进程中,然后按行将它们写出。最简单的方法是

tool1 | while read; do echo "$REPLY"; done & tool2 | while read; do echo "$REPLY"; done

对于多个命令/脚本的“美丽”并行执行,这里你可以阅读我的另一个答案。

3.真正的解决方案

不幸的是,进程输出主要由 libc 缓冲,即它们的内部事物,它们如何将输出映射到write(1, ...)内核级别的调用。这取决于他们。如果不改变它们,我们就无法控制它们的输出,即它们尚未编写的内容。

FILE*如果使用libc 的机制进行写入,则设置缓冲区 (3)冲洗 (3)函数可能是你的朋友。

相关内容