如何在Unix(或者Windows)中使用(最好是未命名的)管道将一个进程的标准输出发送到多个进程?

时间:2020-03-05 18:52:51  来源:igfitidea点击:

我想将进程proc1的标准输出重定向到两个进程proc2和proc3:

proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

我试过了

proc1 | (proc2 & proc3)

但它似乎不起作用,即

echo 123 | (tr 1 a & tr 1 b)

b23

而不是stdout

a23
 b23

解决方案

回答

编者注:

是一种过程替代,它是某些POSIX兼容外壳的非标准外壳功能:bashkshzsh
如所写,答案也意外地通过管道发送了输出过程替换的输出。
回声123 | tee>(tr 1 a)>(tr 1 b)> / dev / null可以防止这种情况,但是有一个陷阱:进程替换的输出将被不可预测地交错,并且,除了在zsh中,管道可能会终止之前>()`中的命令可以执行。

在Unix中(或者在Mac上),使用tee命令:

$ echo 123 | tee >(tr 1 a)  | tr 1 b
b23
a23

通常,我们将使用tee将输出重定向到多个文件,但是使用>(...)我们可以
重定向到另一个进程。所以总的来说

$ proc1 | tee >(proc2) ... >(procN-1) | procN

会做你想要的。

在Windows下,我不认为内置外壳具有等效功能。微软的Windows PowerShell有一个tee命令。

回答

自@dF:提到PowerShell已经开球以来,我想我将展示一种在PowerShell中执行此操作的方法。

PS > "123" | % { 
    $_.Replace( "1", "a"), 
    $_.Replace( "2", "b" ) 
}

a23
1b3

请注意,在创建下一个对象之前,将处理从第一个命令发出的每个对象。这可以允许缩放到非常大的输入。

回答

就像dF所说的一样,bash允许使用运行命令的>>()构造代替文件名。 (也有<()构造来代替另一个命令的输出来代替文件名,但这已经不重要了,我只为完整性而提到它)。

如果我们没有bash或者在具有较旧版本bash的系统上运行,则可以通过使用FIFO文件来手动执行bash的操作。

实现所需目标的通用方法是:

  • 确定应接收命令输出的进程数,并创建尽可能多的FIFO,最好在全局临时文件夹中:
subprocesses="a b c d"
    mypid=$$
    for i in $subprocesses # this way we are compatible with all sh-derived shells  
    do
        mkfifo /tmp/pipe.$mypid.$i
    done
  • 启动所有子进程,等待来自FIFO的输入:
for i in $subprocesses
    do
        tr 1 $i </tmp/pipe.$mypid.$i & # background!
    done
  • 执行准备进入FIFO的命令:
proc1 | tee $(for i in $subprocesses; do echo /tmp/pipe.$mypid.$i; done)
  • 最后,删除FIFO:
for i in $subprocesses; do rm /tmp/pipe.$mypid.$i; done

注意:出于兼容性原因,我会在反引号中使用$(),但我无法编写此答案(反引号用于SO中)。通常,$()足够老,即使在旧版本的ksh中也可以使用,但是如果不行,则将"部分放在反引号中。