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

windows bash unix shell pipe

24854 观看

5回复

1057 作者的声誉

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

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

我试过了

 proc1 | (proc2 & proc3)

但它似乎不起作用,即

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

 b23

而不是stdout

 a23
 b23
作者: secr 的来源 发布者: 2008 年 9 月 13 日

回应 (5)


121

56204 作者的声誉

决定

编者按
- >(…)是一个进程替换是一个非标准壳特征一些 POSIX兼容的外壳:bashkshzsh
-这个答案不小心将通过管道输出过程中替换的输出echo 123 | tee >(tr 1 a) | tr 1 b
-进程替换的输出将以不可预测的方式进行交织,并且,除了in中zsh,管道可能在内部命令>(…)执行之前终止。

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

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

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

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

会做你想要的。

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

作者: dF. 发布者: 13.09.2008 10:37

5

33108 作者的声誉

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

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

a23
1b3

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

作者: Jay Bazuzi 发布者: 14.09.2008 11:16

22

69144 作者的声誉

就像dF所说的那样,bash允许使用>(…)运行命令的结构代替文件名。(还有一种<(…)结构可以用另一个命令的输出代替文件名,但是现在已经不相关了,我只是为了完整性而提到它)。

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

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

  • 确定应该接收命令输出的进程数,并创建尽可能多的FIFO,最好在全局临时文件夹中:
    subprocesses =“ abc d”
    mypid = $$
    对于$ subprocesses中的i#这样,我们与所有sh派生的shell兼容  
    做
        mkfifo /tmp/pipe.$mypid.$i
    做完了
  • 启动所有子进程,等待来自FIFO的输入:
    为我在$ subprocesses中
    做
        tr 1 $ i 

  • 执行准备进入FIFO的命令:
    proc1 | tee $(对于$ subprocesses中的i;执行echo /tmp/pipe.$mypid.$i;完成)
  • 最后,删除FIFO:
    对于我在$ subprocesses中;做rm /tmp/pipe.$mypid.$i; 做完了

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

作者: tzot 发布者: 10.10.2008 10:43

-1

1683 作者的声誉

另一种方法是

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

输出:

a23
b23

无需在此处创建子外壳

作者: munish 发布者: 14.05.2013 11:23

8

164380 作者的声誉

UNIX( ,bash,)kshzsh

dF。的答案包含基于答案种子tee以及可能会或可能不会有效的输出 过程替换
>(...)),具体取决于您的要求:

请注意,进程替换是一种非标准功能,(大多数情况下)支持POSIX仅功能的外壳程序(例如dash/bin/sh在Ubuntu 上起作用)。Shell脚本目标应该依靠他们。/bin/sh

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

这种方法的陷阱是:

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

  • bashksh(相对于zsh-,但请参见下面的例外):

    • 命令完成,输出可能会到达。
    • 后续命令可以开始执行之前的过程中替换的命令已经完成 - bashksh等待输出过程替换生成的进程到结束,至少在默认情况下。
    • jmb在dF的回答中很好地说明了这一点:

请注意,内部启动的命令>(...)与原始Shell无关,并且您无法轻易确定它们何时完成;tee编写完所有内容后,这些操作将完成,但是被替换的进程仍将使用内核和文件I / O中各个缓冲区的数据,以及内部处理数据所花费的时间。如果您的外壳随后继续依赖子流程生成的任何内容,则可能会遇到竞争状况。

  • zsh是唯一的外壳确实默认等待的过程中的输出处理替换运行到完成除了如果是标准错误被重定向到一个(2> >(...))。

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

  • bash v4.4+可以等待最近有推出输出进程替换wait $!,但争论少wait工作,使这个不适合用命令输出过程换人。

  • 然而,bashksh强制等待通过管道命令| cat,但请注意,这使得在运行命令的子shell注意事项

    • ksh(截至ksh 93u+)不支持将stderr发送到输出进程替代(2> >(...));这种尝试被默默地忽略了

    • 虽然默认情况下zsh(最好是)与stdout输出过程替换(最好是同步)同步,但是即使该技术也无法使它们与stderr输出过程替换()同步。| cat2> >(...)

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

以下命令在bash或中运行时ksh,说明了有问题的行为(您可能必须多次运行才能看到两种症状):AFTER通常会在输出替换的输出之前打印,并且输出替换的输出可能会意外地交错。

printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER

简而言之

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

    • 无论是bash也不是ksh也不zsh支持。
  • 同步执行:

    • 可行,除了使用stderr源的输出过程替代:
      • 在中zsh,它们总是异步的。
      • ksh,他们根本不工作

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


请注意,tzot麻烦得多,但可能符合POSIX的解决方案也表现出不可预测的输出行为。但是,通过使用wait可以确保在所有后台进程完成之前,后续命令不会开始执行。
见底部一个更强大,同步,串行输出实现


唯一具有可预测输出行为的简单 bash解决方案是以下解决方案,但是,对于大型输入集,这是令人望而却步的,因为壳循环本来就很慢。
还要注意,这会交替输出目标命令的输出行

while IFS= read -r line; do 
  tr 1 a <<<"$line"
  tr 1 b <<<"$line"
done < <(echo '123')

Unix(使用GNU Parallel)

安装GNUparallel可以实现具有序列化(按命令)输出强大解决方案,该输出还允许并行执行

$ echo '123' | parallel --pipe --tee {} ::: 'tr 1 a' 'tr 1 b'
a23
b23

parallel默认情况下,确保不同命令的输出不会交织(可以修改此行为-参见man parallel)。

注意:某些Linux发行版附带了一个不同的 parallel实用程序,该实用程序无法与以上命令一起使用。用于parallel --version确定您拥有哪一个。


视窗

Jay Bazuzi的有用答案显示了如何在PowerShell中进行操作。就是说:他的答案与bash上面的循环答案类似,对于大型输入集,它的速度将令人望而却步,并且还会交替输出目标命令的输出行



bash基于,但可移植的Unix解决方案,具有同步执行和输出序列化

以下是tzot答案中提供的方法的简单但相当可靠的实现,该方法还提供了:

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

尽管不严格符合POSIX,但由于它是bash脚本,因此应可移植到具有的任何Unix平台上bash

注意:您可以在此Gist中找到根据MIT许可证发布的更全面的实现。

如果将以下代码另存为script fanout,使其可执行并放入int PATH,则问题中的命令将按以下方式工作:

$ echo 123 | fanout 'tr 1 a' 'tr 1 b'
# tr 1 a
a23
# tr 1 b
b23

fanout脚本源代码

#!/usr/bin/env bash

# The commands to pipe to, passed as a single string each.
aCmds=( "$@" )

# Create a temp. directory to hold all FIFOs and captured output.
tmpDir="${TMPDIR:-/tmp}/$kTHIS_NAME-$$-$(date +%s)-$RANDOM"
mkdir "$tmpDir" || exit
# Set up a trap that automatically removes the temp dir. when this script
# exits.
trap 'rm -rf "$tmpDir"' EXIT 

# Determine the number padding for the sequential FIFO / output-capture names, 
# so that *alphabetic* sorting, as done by *globbing* is equivalent to
# *numerical* sorting.
maxNdx=$(( $# - 1 ))
fmtString="%0${#maxNdx}d"

# Create the FIFO and output-capture filename arrays
aFifos=() aOutFiles=()
for (( i = 0; i <= maxNdx; ++i )); do
  printf -v suffix "$fmtString" $i
  aFifos[i]="$tmpDir/fifo-$suffix"
  aOutFiles[i]="$tmpDir/out-$suffix"
done

# Create the FIFOs.
mkfifo "${aFifos[@]}" || exit

# Start all commands in the background, each reading from a dedicated FIFO.
for (( i = 0; i <= maxNdx; ++i )); do
  fifo=${aFifos[i]}
  outFile=${aOutFiles[i]}
  cmd=${aCmds[i]}
  printf '# %s\n' "$cmd" > "$outFile"
  eval "$cmd" < "$fifo" >> "$outFile" &
done

# Now tee stdin to all FIFOs.
tee "${aFifos[@]}" >/dev/null || exit

# Wait for all background processes to finish.
wait

# Print all captured stdout output, grouped by target command, in sequences.
cat "${aOutFiles[@]}"
作者: mklement0 发布者: 11.05.2017 04:51
32x32