我需要做类似的事情这个问题command2
,除了在那个问题中 OP 只是连接和的输出command3
,我需要它们被分别移交,就像这样:
command2 [stream A]
/ \
command1 join -j1 [stream A] [stream B]
\ /
command3 [stream B]
(这里join
是 coreutilsjoin
实用程序,这是我明确命名的唯一实用程序,以明确表示我不希望流 A 和 B 被不加区别地合并)
我尝试过这个:
command1 | tee >(command2 >&3- ) >(command3 >&4- ) >/dev/null | join -j1 /dev/fd/3 /dev/fd/4
但是 bash 有理由抱怨:
bash: 3: Bad file descriptor
bash: 4: Bad file descriptor
(因为文件描述符 3 和 4 尚未打开)
我认为我需要指示 bash 以某种方式调用 2 个额外的命令(就像在 中从左命令通过管道传输到右命令pipe(2)
时一样)。由于每次调用都会创建两个 fd(一个用于写入,另一个用于读取),因此我需要:fd1
fd0
left | right
pipe(2)
- 关闭
command2
读取端,并将标准输出重定向到写入端(管道1); - 关闭
command3
读取端,并将标准输出重定向到写入端(管道2); - 用于
join
关闭书写两端并指示其打开/dev/fd/reading-end-of-pipe1
和/dev/fd/reading-end-of-pipe2
我不一定具有此环境中任何路径的写权限,因此无法使用mkfifo
。
答案1
理论上你可以用协同处理。要么是我太笨了,要么就是管理像 这样的描述符${COPROC[1]}
(这样它们就可以在我想要的地方使用,并在我想要关闭它们的时候和地点关闭)真的很麻烦。我试过了,但失败了。我找到了一种不那么麻烦的方法,但它需要/proc
。
我的Bash版本是5.0.3。
使用起来相对简单,coproc
只需设置相关管道,然后/proc/…/fd/…
在需要的地方使用即可。
启动协同进程:
coproc command2
构建其余管道。有一个怪癖。我希望
command2
过滤器仅在其 stdin 或 stdout 关闭后退出。shell 保持这些打开,如果我们分离,则协同进程将被终止。因此,在我们附加真正使用协同进程的进程之前,我们不能分离。另一方面,如果 shell 没有分离,则协同进程将不会终止,其他进程可能会等待它。这意味着我们必须与 shell 异步运行它们,然后稍后分离 shell,否则整个设置可能会阻塞。构建其余管道并异步运行它:command1 | tee "/proc/$COPROC_PID/fd/0" | command3 | join -j1 - "/proc/$COPROC_PID/fd/1" &
将 shell 从协进程中分离出来。在我的测试中,用可用的数字关闭描述符就足够了
${COPROC[1]}
。操作方法如下:exec {COPROC[1]}>&-
请注意,上面的语法实际上并不使用
${COPROC[1]}
。$
相当不直观。在关闭描述符之前,您可以看到数字:echo "${COPROC[1]}"
。假设它打印60
。exec 60>&-
是一个有效的命令,但不要尝试exec "${COPROC[1]}">&-
。后一种语法将“工作”如下exec 60 >&-
,它将关闭 shell 的标准输出!现在您可以
wait
使用我们的管道了;或者fg
如果启用了作业控制,您也可以使用它。
笔记:
如果启用了作业控制(在交互式 Bash 中默认启用),并且
command1
想要从终端读取某些内容,则它将无法读取,直到您fg
这样做。当禁用作业控制时,情况会有所不同,例如在脚本中。如果禁用作业控制,则某些 shell 会将 stdin 重定向command1
到/dev/null
或某个等效文件。这就是&
工作原理。但是 Bash 不会对管道执行此操作。不幸的是,存在竞争条件:
exec {COPROC[1]}>&-
在我们的异步命令设法打开之前,可能会执行并终止协同进程/proc/$COPROC_PID/fd/…
。一个不太优雅的“修复”是延迟sleep
一段时间。我不太喜欢这个(理论上延迟并不能保证任何事情,它只是降低了失败的概率)。一个强大的修复方法是在将要exec
打开的命令的子 shell 中打开管道…/fd/0
,然后向主 shell 发出信号,让它可以安全地关闭描述符。coproc command2 trap 'exec {COPROC[1]}>&-' USR1 command1 | ( exec 3>"/proc/$COPROC_PID/fd/0" 4<"/proc/$COPROC_PID/fd/1"; kill -s USR1 "$$"; exec tee "/proc/$COPROC_PID/fd/0" ) | command3 | join -j1 - "/proc/$COPROC_PID/fd/1" & wait wait
我们需要两个
wait
s,因为第一个 swait
可能会被信号打断。
概念验证
#!/bin/bash
set +m # job control explicitly disabled
coproc sed 's/$/ added_by_sed/'
trap 'exec {COPROC[1]}>&-' USR1
echo 'Type few lines and press Ctrl+d (twice if needed).'
cat -n | ( exec 3>"/proc/$COPROC_PID/fd/0" 4<"/proc/$COPROC_PID/fd/1"; kill -s USR1 "$$"; exec tee "/proc/$COPROC_PID/fd/0" ) | awk '{print $0,"added_by_awk"}' | join -j1 - "/proc/$COPROC_PID/fd/1" &
wait
wait