如何使用 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
How can I send the stdout of one process to multiple processes using (preferably unnamed) pipes in Unix (or Windows)?
提问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兼容的外壳:bash
,ksh
,zsh
。
-这个答案不小心将通过管道输出过程中替换的输出也:echo 123 | tee >(tr 1 a) | tr 1 b
。
- 进程替换的输出将不可预测地交错,并且除了 in 之外zsh
,管道可能会在>(…)
do内部的命令之前终止。
In unix (or on a mac), use the tee
command:
在 unix(或 Mac)中,使用tee
命令:
$ echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
b23
a23
Usually you would use tee
to 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 tee
command though.
在 Windows 下,我认为内置 shell 没有等效项。不过微软的Windows PowerShell有一个tee
命令。
回答by tzot
Like dF said, bash
allows 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 tee
and 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/sh
on Ubuntu,
for instance), do notsupport. Shell scripts targeting /bin/sh
should 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
bash
andksh
(as opposed tozsh
- but see exception below):- output may arrive afterthe command has finished.
- subsequent commands may start executing beforethe commands in the process substitutions have finished-
bash
andksh
do notwait for the output process substitution-spawned processes to finish, at least by default. - jmbputs it well in a comment on dF.'s answer:
不可预测的异步输出行为:来自输出过程替换内部命令的输出流
>(...)
以不可预测的方式交错。在
bash
和ksh
(相对于zsh
- 但见下面的例外):- 命令完成后,输出可能会到达。
- 后续命令可以开始执行之前的过程中替换的命令已经完成-
bash
和ksh
不不等待输出过程替换生成的进程到结束,至少在默认情况下。 - jmb在对 dF. 的回答的评论中说得很好:
be aware that the commands started inside
>(...)
are dissociated from the original shell, and you can't easily determine when they finish; thetee
will 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缓冲区的各种数据,以及任何时间采取的数据的内部处理。如果你的外壳继续依赖子进程产生的任何东西,你可能会遇到竞争条件。
zsh
is 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 version93u+
) allows use of argument-lesswait
to 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 withwait $!
, but argument-lesswait
does notwork, making this unsuitable for a command with multipleoutput process substitutions.However,
bash
andksh
can be forcedto waitby piping the command to| cat
, but note that this makes the command run in a subshell. Caveats:ksh
(as ofksh 93u+
) doesn't support sending stderrto an output process substitution (2> >(...)
); such an attempt is silently ignored.While
zsh
is (commendably) synchronous by defaultwith the (far more common) stdoutoutput process substitutions, even the| cat
technique 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
做不工作,使这个不适合用命令多输出过程换人。然而,
bash
和ksh
可强制等待通过管道命令| cat
,但请注意,这使得在运行命令的子shell。注意事项:ksh
(截至ksh 93u+
)不支持将stderr发送到输出进程替换 (2> >(...)
);这样的尝试被默默无视。虽然默认情况下
zsh
(值得称赞)与(更常见的)stdout输出进程替换同步,但即使该技术也无法使它们与stderr输出进程替换同步( )。| cat
2> >(...)
但是,即使您确保同步执行,不可预测的交错输出问题仍然存在。
The following command, when run in bash
or ksh
, illustrates the problematic behaviors (you may have to run it several times to see bothsymptoms): The AFTER
will typically print beforeoutput from the output substitutions, and the output from the latter can be interleaved unpredictably.
下面的命令,在运行时bash
或ksh
,说明了问题行为(您可能需要运行几次看到这两个症状等):在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
bash
norksh
norzsh
support that.
- Neither
Synchronous execution:
- Doable, except with stderr-sourced output process substitutions:
- In
zsh
, they're invariablyasynchronous. - In
ksh
, they don't work at all.
- In
- Doable, except with stderr-sourced output process substitutions:
保证特定的 per-command 输出序列:
- 无论是
bash
也不是ksh
也不zsh
支持。
- 无论是
同步执行:
- 可行,除了stderr 来源的输出过程替换:
- 在 中
zsh
,它们总是异步的。 - 在 中
ksh
,它们根本不起作用。
- 在 中
- 可行,除了stderr 来源的输出过程替换:
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 wait
you 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 straightforwardbash
solution 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 parallel
enables 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
parallel
by 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 differentparallel
utility, which won't work with the command above; use parallel --version
to 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 bash
answer 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 bash
script, 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
fanout
script 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