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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/60942/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 05:16:53  来源:igfitidea点击:

How can I send the stdout of one process to multiple processes using (preferably unnamed) pipes in Unix (or Windows)?

windowsbashunixshellpipe

提问by secr

I'd like to redirect the stdout of process proc1 to two processes proc2 and proc3:

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

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

I tried

我试过

 proc1 | (proc2 & proc3)

but it doesn't seem to work, i.e.

但它似乎不起作用,即

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

writes

 b23

to stdout instead of

到标准输出而不是

 a23
 b23

回答by dF.

Editor's note:
- >(…)is a process substitutionthat is a nonstandard shell featureof somePOSIX-compatible shells: bash, ksh, zsh.
- This answer accidentally sends the output process substitution's output through the pipeline too: echo 123 | tee >(tr 1 a) | tr 1 b.
- Output from the process substitutions will be unpredictably interleaved, and, except in zsh, the pipeline may terminate before the commands inside >(…)do.

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

In unix (or on a mac), use the teecommand:

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

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

Usually you would use teeto redirect output to multiple files, but using >(...) you can redirect to another process. So, in general,

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

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

will do what you want.

会做你想做的。

Under windows, I don't think the built-in shell has an equivalent. Microsoft's Windows PowerShellhas a teecommand though.

在 Windows 下,我认为内置 shell 没有等效项。不过微软的Windows PowerShell有一个tee命令。

回答by tzot

Like dF said, bashallows to use the >(…)construct running a command in place of a filename. (There is also the <(…)construct to substitute the outputof another command in place of a filename, but that is irrelevant now, I mention it just for completeness).

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

If you don't have bash, or running on a system with an older version of bash, you can do manually what bash does, by making use of FIFO files.

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

The generic way to achieve what you want, is:

实现您想要的通用方法是:

  • decide how many processes should receive the output of your command, and create as many FIFOs, preferably on a global temporary folder:
  • 决定有多少进程应该接收命令的输出,并创建尽可能多的 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
  • start all your subprocesses waiting input from the FIFOs:
  • 启动所有子进程,等待来自 FIFO 的输入:
    for i in $subprocesses
    do
        tr 1 $i </tmp/pipe.$mypid.$i & # background!
    done
  • execute your command teeing to the FIFOs:
  • 对 FIFO 执行您的命令:
    proc1 | tee $(for i in $subprocesses; do echo /tmp/pipe.$mypid.$i; done)
  • finally, remove the FIFOs:
  • 最后,删除 FIFO:
    for i in $subprocesses; do rm /tmp/pipe.$mypid.$i; done

NOTE: for compatibility reasons, I would do the $(…)with backquotes, but I couldn't do it writing this answer (the backquote is used in SO). Normally, the $(…)is old enough to work even in old versions of ksh, but if it doesn't, enclose the part in backquotes.

注意:出于兼容性原因,我会$(…)用反引号来做,但我不能写这个答案(反引号在 SO 中使用)。通常,$(…)即使在旧版本的 ksh 中也足够老,但如果没有,请将该部分括在反引号中。

回答by mklement0

Unix (bash, ksh, zsh)

Unix ( bash, ksh, zsh)

dF.'s answercontains the seedof an answer based on teeand outputprocess substitutions
(>(...)) that may or may notwork, depending on your requirements:

dF. 的答案包含基于和输出过程替换( )的答案种子,该替换可能有效也可能无效,具体取决于您的要求:tee
>(...)

Note that process substitutions are a nonstandardfeature that (mostly) POSIX-features-only shells such as dash(which acts as /bin/shon Ubuntu, for instance), do notsupport. Shell scripts targeting /bin/shshould notrely on them.

请注意,进程替换是一项非标准功能,(大多数)仅支持 POSIX 功能的 shell(例如dash(例如/bin/sh在 Ubuntu上充当)支持)。Shell脚本目标/bin/sh应该依靠他们。

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

The pitfallsof this approach are:

这种方法的缺陷是:

  • unpredictable, asynchronous output behavior: the output streams from the commands inside the output process substitutions >(...)interleave in unpredictable ways.

  • In bashand ksh(as opposed to zsh- but see exception below):

    • output may arrive afterthe command has finished.
    • subsequent commands may start executing beforethe commands in the process substitutions have finished- bashand kshdo notwait for the output process substitution-spawned processes to finish, at least by default.
    • jmbputs it well in a comment on dF.'s answer:
  • 不可预测的异步输出行为:来自输出过程替换内部命令的输出流>(...)以不可预测的方式交错。

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

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

be aware that the commands started inside >(...)are dissociated from the original shell, and you can't easily determine when they finish; the teewill finish after writing everything, but the substituted processes will still be consuming the data from various buffers in the kernel and file I/O, plus whatever time is taken by their internal handling of data. You can encounter race conditions if your outer shell then goes on to rely on anything produced by the sub-processes.

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

  • zshis the only shell that doesby default wait for the processes run in the output process substitutions to finish, exceptif it is stderrthat is redirected to one (2> >(...)).

  • ksh(at least as of version 93u+) allows use of argument-less waitto wait for the output process substitution-spawned processes to finish.
    Note that in an interactive session that could result in waiting for any pending background jobstoo, however.

  • bash v4.4+can wait for the most recentlylaunched output process substitution with wait $!, but argument-less waitdoes notwork, making this unsuitable for a command with multipleoutput process substitutions.

  • However, bashand kshcan be forcedto waitby piping the command to | cat, but note that this makes the command run in a subshell. Caveats:

    • ksh(as of ksh 93u+) doesn't support sending stderrto an output process substitution (2> >(...)); such an attempt is silently ignored.

    • While zshis (commendably) synchronous by defaultwith the (far more common) stdoutoutput process substitutions, even the | cattechnique cannot make them synchronous with stderroutput process substitutions (2> >(...)).

  • However, even if you ensure synchronous execution, the problem of unpredictably interleaved outputremains.

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

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

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

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

    • ksh(截至ksh 93u+)不支持将stderr发送到输出进程替换 ( 2> >(...));这样的尝试被默默无视

    • 虽然默认情况下zsh(值得称赞)与(更常见的)stdout输出进程替换同步,但即使该技术也无法使它们与stderr输出进程替换同步( )。| cat2> >(...)

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

The following command, when run in bashor ksh, illustrates the problematic behaviors (you may have to run it several times to see bothsymptoms): The AFTERwill typically print beforeoutput from the output substitutions, and the output from the latter can be interleaved unpredictably.

下面的命令,在运行时bashksh,说明了问题行为(您可能需要运行几次看到这两个症状等):在AFTER通常打印之前,从输出换人输出,并从后者输出可不可预知的交错。

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

In short:

简而言之

  • Guaranteeing a particular per-command output sequence:

    • Neither bashnor kshnor zshsupport that.
  • Synchronous execution:

    • Doable, except with stderr-sourced output process substitutions:
      • In zsh, they're invariablyasynchronous.
      • In ksh, they don't work at all.
  • 保证特定的 per-command 输出序列:

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

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

If you can live with these limitations, using output process substitutions is a viable option (e.g., if all of them write to separate output files).

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



Note that tzot's much more cumbersome, but potentially POSIX-compliant solutionalso exhibits unpredictable output behavior; however, by using waityou can ensure that subsequent commands do not start executing until all background processes have finished.
See bottomfor a more robust, synchronous, serialized-output implementation.

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



The only straightforwardbashsolution with predictable output behavioris the following, which, however, is prohibitively slow with large input sets, because shell loops are inherently slow.
Also note that this alternatesthe output lines from the target commands.

具有可预测输出行为的唯一直接bash解决方案是以下解决方案,但是,对于大输入集来说速度非常慢,因为 shell 循环本质上很慢。
另请注意,这会交替目标命令的输出行

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


Unix (using GNU Parallel)

Unix(使用 GNU Parallel)

Installing GNU parallelenables a robust solutionwith serialized (per-command) outputthat additionally allows parallel execution:

安装GNUparallel启用了具有序列化(按命令)输出强大解决方案,另外还允许并行执行

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

parallelby default ensures that output from the different commands doesn't interleave (this behavior can be modified - see man parallel).

parallel默认情况下确保来自不同命令的输出不会交错(可以修改此行为 - 请参阅man parallel)。

Note: Some Linux distros come with a differentparallelutility, which won't work with the command above; use parallel --versionto determine which one, if any, you have.

注意:一些 Linux 发行版带有不同的parallel实用程序,它不适用于上面的命令;用于parallel --version确定您拥有哪一个(如果有)。



Windows

视窗

Jay Bazuzi's helpful answershows how to do it in PowerShell. That said: his answer is the analog of the looping bashanswer above, it will be prohibitively slow with large input setsand also alternatesthe output lines from the target commands.

Jay Bazuzi 的有用答案显示了如何在PowerShell 中执行此操作。也就是说:他的答案是bash上面循环答案的模拟,对于大输入集,它会非常慢,并且还会交替来自目标命令的输出行





bash-based, but otherwise portable Unix solution with synchronous execution and output serialization

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

The following is a simple, but reasonably robust implementation of the approach presented in tzot's answerthat additionally provides:

以下是tzot 回答中提出的方法的一个简单但相当稳健的实现,该方法还提供:

  • synchronous execution
  • serialized (grouped) output
  • 同步执行
  • 序列化(分组)输出

While not strictly POSIX compliant, because it is a bashscript, it should be portable to any Unix platform that has bash.

虽然不是严格的 POSIX 兼容,因为它是一个bash脚本,它应该可以移植到任何具有bash.

Note: You can find a more full-fledged implementation released under the MIT license in this Gist.

注意:您可以在此 Gist 中找到在 MIT 许可下发布的更成熟的实现。

If you save the code below as script fanout, make it executable and put int your PATH, the command from the question would work as follows:

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

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

fanoutscript source code:

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[@]}"

回答by Jay Bazuzi

Since @dF: mentioned that PowerShell has tee, I thought I'd show a way to do this in PowerShell.

由于@dF: 提到 PowerShell 有 tee,我想我会展示一种在 PowerShell 中执行此操作的方法。

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

a23
1b3

Note that each object coming out of the first command is processed before the next object is created. This can allow scaling to very large inputs.

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

回答by munish

another way to do would be,

另一种方法是,

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

output:

输出:

a23
b23

no need to create a subshell here

无需在此处创建子shell