Bash 拆分流(tee)并将它们连接在一起

Bash 拆分流(tee)并将它们连接在一起

我需要做类似的事情这个问题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(一个用于写入,另一个用于读取),因此我需要:fd1fd0left | rightpipe(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/…在需要的地方使用即可。

  1. 启动协同进程:

    coproc command2
    
  2. 构建其余管道。有一个怪癖。我希望command2过滤器仅在其 stdin 或 stdout 关闭后退出。shell 保持这些打开,如果我们分离,则协同进程将被终止。因此,在我们附加真正使用协同进程的进程之前,我们不能分离。另一方面,如果 shell 没有分离,则协同进程将不会终止,其他进程可能会等待它。这意味着我们必须与 shell 异步运行它们,然后稍后分离 shell,否则整个设置可能会阻塞。构建其余管道并异步运行它:

    command1 | tee "/proc/$COPROC_PID/fd/0" | command3 | join -j1 - "/proc/$COPROC_PID/fd/1" &
    
  3. 将 shell 从协进程中分离出来。在我的测试中,用可用的数字关闭描述符就足够了${COPROC[1]}。操作方法如下:

    exec {COPROC[1]}>&-
    

    请注意,上面的语法实际上并不使用${COPROC[1]}$相当不直观。在关闭描述符之前,您可以看到数字:echo "${COPROC[1]}"。假设它打印60exec 60>&-是一个有效的命令,但不要尝试exec "${COPROC[1]}">&-。后一种语法将“工作”如下exec 60 >&-,它将关闭 shell 的标准输出!

  4. 现在您可以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
    

    我们需要两个waits,因为第一个 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

相关内容