从 bash 代码
command1 | tee >(command2) | command3
我想捕获in的输出和command2
invar2
的输出。command3
var3
command1
是 I/O 绑定的,其他命令成本很高,但可以在command1
完成之前开始工作。
command2
和的输出顺序command3
不固定。所以我尝试在
read -r var2 <<< var3=(command1 | tee >(command2 >&3) | command3) 3>&1
或者
{read -u 3 -r var2; read -r var3} <<< command1 | tee >(command2 >&3) | command3
但没有成功。
有没有办法让三个命令并行运行,将结果存储在不同的变量中而不是制作临时文件?
因此,您想将 cmd1 的输出通过管道传输到 cmd2 和 cmd3 中,并将 cmd2 和 cmd3 的输出都输入到不同的变量中?
然后看起来你需要来自 shell 的两个管道,一个连接到 cmd2 的输出,一个连接到 cmd3 的输出,并且 shell 使用
select()
/poll()
从这两个管道中读取。bash
不会这样做,你需要一个更高级的 shell,比如zsh
.zsh
没有原始接口,但如果在 Linux 上,您可以使用在常规管道上的行为类似于命名管道pipe()
的事实,并使用与在使用 shell 重定向读取/写入相同文件描述符时使用的方法类似的方法/dev/fd/x
如果我很好地理解了您的所有要求,您可以
bash
通过为每个命令创建一个未命名管道,然后将每个命令的输出重定向到其各自的未命名管道,最后将每个输出从其管道检索到一个单独的变量中来实现。因此,解决方案可能如下:
这里特别注意:
<(:)
;这是一个未记录的 Bash 打开“未命名”管道的技巧echo EOF
的方法来通知while
循环不再有输出。这是必要的,因为只关闭未命名的管道(通常会结束任何while read
循环)是没有用的,因为这些管道是双向的,即用于写入和读取。我不知道如何将它们打开(或转换)成通常的一对文件描述符,一个是读取端,另一个是写入端。在此示例中,我使用了纯 bash 方法(除了使用
tee
)来更好地阐明使用这些未命名管道所需的基本算法,但是您可以使用几个sed
代替while
循环来完成这两个分配,就像var2="$(sed -ne '/^EOF$/q;p' <&${pipe2})"
变量 2 和它各自的变量 3 一样,产生相同的结果,输入更少。也就是说,整个事情将是:少量数据的精益解决方案
为了显示目标变量,请记住通过清除 IFS 来禁用分词,如下所示:
否则你会在输出中丢失换行符。
以上看起来确实是一个非常干净的解决方案。不幸的是,它仅适用于不太大的输出,并且您的里程可能会有所不同:在我的测试中,我遇到了大约 530k 输出的问题。如果你在 4k 的(非常保守的)限制之内,你应该没问题。
该限制的原因在于这样的两个赋值,即命令替换语法,是同步操作,这意味着第二个赋值仅在第一个完成后运行,而相反,同时
tee
馈送两个命令并阻塞所有如果碰巧填满了它的接收缓冲区。一个僵局。解决这个问题需要一个稍微复杂的脚本,以便同时清空两个缓冲区。为此,
while
两个管道上的环路会派上用场。任何数据量的更标准的解决方案
更标准的 Bashism 是这样的:
在这里,您将来自两个命令的行多路复用到单个标准“stdout”文件描述符,然后随后将合并的输出多路分解到每个相应的变量上。
特别注意:
sed
命令在每个输出行前面加上字符串“cmd2:”或“cmd3:”(分别),以便脚本知道每行属于哪个变量stdbuf -oL
:这是因为这里的两个命令共享相同的输出文件描述符,因此如果它们碰巧流式传输,它们很容易在最典型的竞争条件下覆盖彼此的输出同时输出数据;行缓冲输出有助于避免这种情况正确显示此类索引数组的一种安全方法是:
当然你也可以只使用
"${var2[*]}"
如下:但是当有很多行时,这不是很有效。
我发现了一些似乎很好用的东西:
<(:)
它的工作原理是为文件描述符 3设置一个匿名管道并将输出管道command2
传输到它。var3
捕获文件描述符 3 的输出command3
并从文件描述符 3 读取最后一行,直到它在 0.01 秒内没有收到任何新数据。它仅适用于最多 65536 字节的输出,
command2
其中似乎由匿名管道缓冲。我不喜欢解决方案的最后一行。我宁愿一次读取所有内容,而不是等待 0.01 秒,而是在缓冲区为空时立即停止。但我不知道有什么更好的方法。
这是从 4.0 开始可以实现的。bash 添加了一个shell 保留字coproc。它将跟随在后台的命令作为子进程分叉(通常不允许传递变量。)但是它创建一个数组(默认为 COPROC,但可以命名)。${COPROC[0]} 连接到子流程的标准输入 ${COPROC 1 } 它的标准输出。这些流程可以作为作业进行操作,是异步的,因此您只需使用 tee 管道连接到两个协同流程并拥有它们分别输出到一个单独的文件,然后通过调用 "${COPROCFIRST 1 } ${COPROCSECOND 1 }"将每个返回值的输出组合起来,这很棒,因为它甚至不需要在管道中。
bash coproc command_1 { command1 arg1 arg2 arg3 >> command_1_output.txt } coproc command_2 { command2 arg1 arg2 arg3 >> command_2_output.txt } 其他命令 | tee >^${command_1 1 }" >&"${command_2 1 }" 读取 -r results1 <&"${command_1[0]" 读取 -r resluts2 <&"${command_2[0]" echo "$result1 $result2" | command3 >> combineresult.txt如前所述,该解决方案目前不完整,因此我很震惊,但是原理是合理的,并且与上面的答案相似。但是,我将指导您阅读有关该主题的一些好文章,并在工作允许时返回此答案。
在协同进程中设置变量的示例
深入了解协同进程和命名管道
用途和陷阱