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

时间:2008-09-13 22:28:37

标签: windows bash unix shell pipe

我想将进程proc1的stdout重定向到两个进程proc2和proc3:

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

我试过

 proc1 | (proc2 & proc3)

但它似乎不起作用,即

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

写入

 b23

到stdout而不是

 a23
 b23

6 个答案:

答案 0 :(得分:120)

编者注:
  - >(…)process substitution,是某些 POSIX兼容shell的非标准shell功能bash,{{1} },ksh
  - 这个答案意外地通过管道发送输出过程替换的输出zsh
  - 流程替换的输出将是不可预测的交错,除了echo 123 | tee >(tr 1 a) | tr 1 b之外,管道可能在zsh内的命令之前终止。

在unix(或在Mac上),使用tee command

>(…)

通常你会使用$ echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null b23 a23 将输出重定向到多个文件,但使用>(...)你可以 重定向到另一个进程。所以,一般来说,

tee

会做你想做的事。

在Windows下,我认为内置shell没有等价物。微软的Windows PowerShell有一个$ proc1 | tee >(proc2) ... >(procN-1) >(procN) >/dev/null 命令。

答案 1 :(得分:22)

与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中工作,但如果没有,请将部分包含在反引号中。

答案 2 :(得分:7)

Unix(bashkshzsh

dF.'s answer包含基于tee输出 process substitutions的答案的种子>(...)可能会或可能不会工作,具体取决于您的要求:

请注意,进程替换是非标准功能(主要)  POSIX-features-only shell,例如dash(在Ubuntu上充当/bin/sh,  例如),做支持。定位/bin/sh的Shell脚本应该依赖它们。

echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null

这种方法的陷阱是:

  • 不可预测的异步输出行为:输出流程替换>(...)内的命令的输出流以不可预测的方式交错。

  • bashksh中(与zsh相对 - 但请参见下面的例外情况):

    • 输出可能在命令完成后到达
    • 后续命令可能会在过程替换中的命令完成之前开始执行 - bashksh 等待输出进程替换生成的进程完成,至少在默认情况下是这样。
    • jmb在对dF的评论中做得很好。答案:
  

请注意,>(...)内启动的命令与原始shell分离,您无法轻松确定它们何时完成;写入所有内容后tee将完成,但替换进程仍将消耗内核和文件I / O中各种缓冲区的数据,以及内部数据处理所花费的时间。如果你的外壳继续依赖子过程产生的任何东西,你可能会遇到竞争条件。

  • zsh 默认的唯一shell,等待输出流程替换中的进程完成除了,如果 stderr 被重定向到一个(2> >(...))。

  • ksh (至少从版本93u+开始)允许使用无参数wait等待输出进程替换 - 生成完成的过程。
    但请注意,在交互式会话中,也可能导致等待任何挂起的后台作业

  • bash v4.4+ 可以等待最近启动的输出流程替换wait $!,但无参数{{1} 工作,使其不适合使用多个输出流程替换的命令。

  • 但是, waitbash可以强制等待,方法是将命令汇总到 {{1} } ,但请注意,这会使命令在子shell 中运行。 注意事项

    • ksh(自| cat起)并不支持将 stderr 发送到输出流程替换(ksh);这样的尝试是默默忽略

    • 虽然ksh 93u+(值得称道)默认同步 stdout 输出流程替换,但是{{1}技术不能使它们与 stderr 输出过程替换(2> >(...))同步。

  • 但是,即使您确保同步执行不可预测的交错输出的问题仍然存在。

以下命令在zsh| cat中运行时,说明了有问题的行为(您可能需要多次运行才能看到两个症状):{ {1}}通常会在输出替换的输出之前打印,并且后者的输出可以不可预测地交错。

2> >(...)

简而言之

  • 保证特定的每命令输出序列:

    • bashkshAFTER都不支持。
  • 同步执行:

    • 可行,但 stderr -sourced输出流程替换除外:
      • printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER 中,他们总是异步。
      • bash,他们根本无法工作

如果您可以忍受这些限制,那么使用输出流程替换是一个可行的选择(例如,如果它们都写入单独的输出文件)。

请注意, tzot's much more cumbersome, but potentially POSIX-compliant solution也表现出不可预测的输出行为;但是,通过使用ksh,您可以确保后续命令在所有后台进程完成后才开始执行 参见 更强大,同步,序列化的输出实现

具有可预测输出行为 的唯一简单 zsh解决方案如下所示,然而,令人望而却步大输入集慢,因为shell循环本质上很慢 另请注意,此 会替换目标命令的输出行

zsh

Unix(使用GNU Parallel)

通过序列化(按命令)输出,安装GNU parallel可启用强大解决方案,此外还允许并行执行

ksh
默认情况下,

wait可确保不同命令的输出不会交错(可以修改此行为 - 请参阅bash)。

注意:某些Linux发行版带有不同的 while IFS= read -r line; do tr 1 a <<<"$line" tr 1 b <<<"$line" done < <(echo '123') 实用程序,它不能使用上面的命令;使用$ echo '123' | parallel --pipe --tee {} ::: 'tr 1 a' 'tr 1 b' a23 b23 来确定您拥有哪一个。

Jay Bazuzi's helpful answer显示如何在 PowerShell 中执行此操作。这就是说:他的答案是上面循环parallel答案的模拟,对于大输入集, 交替输出过于缓慢来自目标命令的行

man parallel - 基于同步执行和输出序列化的便携式Unix解决方案

以下是tzot's answer中提出的方法的一个简单但相当强大的实现,另外提供:

  • 同步执行
  • 序列化(分组)输出

虽然不是严格遵守POSIX,但因为它是一个parallel脚本,它应该可移植到任何具有parallel --version 的Unix平台。

注意:您可以在this Gist的MIT许可下找到更全面的实施。

如果您将下面的代码保存为脚本bash,将其设置为可执行文件并将其放入bash,则问题中的命令将如下所示:

bash

bash脚本源代码

fanout

答案 3 :(得分:5)

由于@dF:提到PowerShell有T恤,我想我会在PowerShell中展示一种方法。

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

a23
1b3

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

答案 4 :(得分:1)

您还可以将输出保存在变量中,并将其用于其他进程:

out=$(proc1); echo "$out" | proc2; echo "$out" | proc3

但是,仅在

时有效
  1. proc1在某点终止:-)
  2. proc1不会产生太多输出(不知道有什么限制,但可能是您的RAM)

但是很容易记住,并且可以从在此生成的进程获得的输出上有更多选择,例如e。 g。:

out=$(proc1); echo $(echo "$out" | proc2) / $(echo "$out" | proc3) | bc

我很难用| tee >(proc2) >(proc3) >/dev/null的方法来做类似的事情。

答案 5 :(得分:-1)

另一种方法是,

 eval `echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'`

输出:

a23
b23

无需在此处创建子shell