我想将进程proc1的stdoutredirect到两个进程proc2和proc3:
proc2 -> stdout / proc1 \ proc3 -> stdout
我试过了
proc1 | (proc2 & proc3)
但它似乎并没有工作,即
echo 123 | (tr 1 a & tr 1 b)
写
b23
stdout而不是
a23 b23
编者按 :
– >(…)
是一个进程替换 ,它是一些 POSIX兼容shell的非标准shell特性 : bash
, ksh
, zsh
。
– 正如所写,答案也意外地通过管道发送输出过程替代的输出。
– echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
echo 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下,我不认为内置的shell有相同的。 尽管微软的Windows PowerShell有一个tee
命令。
就像dF所说, bash
允许使用>(…)
构造运行一个命令来代替文件名。 (还有<(…)
构造来代替另一个命令的输出来代替文件名,但现在是不相关的,我只是为了完整性)。
如果你没有bash,或者运行在带有旧版本bash的系统上,你可以通过使用FIFO文件来手动执行bash的功能。
达到你想要的通用方法是:
子过程= “ABCD” mypid = $$ 因为我在$ subprocesses#这样我们就可以兼容所有的sh派生的shell 做 mknod /tmp/pipe.$mypid.$ip DONE
为我在$ subprocesses 做 tr 1 $ i </tmp/pipe.$mypid.$i&#background! DONE
proc1 | tee $(我在$ subprocesses中;做echo /tmp/pipe.$mypid.$i; done)
为我在$ subprocesses; 做rm /tmp/pipe.$mypid.$i; DONE
注:为了兼容性的原因,我会做$(…)
反引号,但我不能这样做写这个答案(反引号用于SO)。 通常情况下, $(…)
已经足够大,即使在旧版本的ksh中也可以工作,但是如果不行的话,请将…
部分包含在反引号中。
由于@dF:提到PowerShell有三通,我想我会在PowerShell中展示一种方法。
PS > "123" | % { $_.Replace( "1", "a"), $_.Replace( "2", "b" ) } a23 1b3
请注意,在创建下一个对象之前处理从第一个命令出来的每个对象。 这可以允许缩放到非常大的输入。
bash
, ksh
, zsh
) dF。的答案包含基于tee
和输出 过程替换 的答案的种子
( >(...)
) 可能会或可能不会工作,这取决于您的要求:
请注意,进程替换是一个非标准功能,主要是POSIX-features-only shell(例如,在Ubuntu上充当/bin/sh
) 不支持。 针对/bin/sh
Shell脚本不应该依赖它们。
echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
这种方法的缺陷是:
不可预知的异步输出行为 :输出流程中的命令输出流将以不可预知的方式进行交替替换>(...)
。
在bash
和ksh
(而不是zsh
– 但请参阅下面的例外):
bash
和ksh
不会等待输出进程替换 – 产生的进程完成,至少在缺省情况下。 注意在
>(...)
内部启动的命令与原始shell分离,并且不能轻易确定它们何时完成; 在写完所有事情之后,tee
将会完成,但是被替换的进程仍然会消耗来自内核和文件I / O中各种缓冲区的数据,以及内部处理数据所花费的时间。 如果您的外壳继续依赖子流程产生的任何东西,您可能会遇到竞争条件。
zsh
是默认情况下等待输出流程中的进程替换完成的唯一shell, 除非它是stderr被重定向到一个( 2> >(...)
)。
ksh
(至少在93u+
版本中)允许使用无参数的wait
来等待输出进程替换 – 产生的进程完成。
请注意,在交互式会话中,可能导致等待任何待处理的后台作业 。
bash v4.4+
可以等待最近启动的输出进程替换wait $!
,但无参数的wait
不起作用,使得这不适用于具有多个输出过程替换的命令。
但是, 可以通过将命令输送到| cat
来迫使 bash
和ksh
等待 | cat
,但请注意,这使命令运行在一个子shell 。 注意事项 :
ksh
(从ksh 93u+
)不支持将stderr发送到输出进程替换( 2> >(...)
); 这样的尝试是无声无息的 。
尽管默认情况下 zsh
与(更常见的) 标准输出输出过程替换( 默认 )是同步的 ,但即使| cat
| cat
技术不能使它们与stderr输出过程替换同步( 2> >(...)
)。
但是, 即使确保同步执行 ,仍然存在不可预测的交错输出问题。
下面的命令在bash
或ksh
运行时,说明了有问题的行为(您可能需要多次运行才能看到两个症状): AFTER
通常会在输出替换输出之前打印,而后者的输出可以是交错的不可预测的。
printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER
总之 :
保证一个特定的每个命令输出序列:
bash
, ksh
和zsh
都不支持。 同步执行:
zsh
,它们总是异步的。 ksh
,他们根本不工作 。 如果你能忍受这些限制,使用输出过程替换是一个可行的选择(例如,如果他们全部写入单独的输出文件)。
请注意, tzot更麻烦,但潜在POSIX兼容的解决方案也表现出不可预知的输出行为 ; 但是,通过使用wait
,可以确保后续命令不会开始执行,直到所有后台进程完成。
请参阅底部以获得更健壮的同步串行输出实现 。
具有可预测的输出行为的唯一简单的 bash
解决方案如下所示,然而, 对于大型输入集合来说 ,这是非常慢的 ,因为shell循环本身就很慢。
另请注意,这将交替输出来自目标命令的行 。
while IFS= read -r line; do tr 1 a <<<"$line" tr 1 b <<<"$line" done < <(echo '123')
安装GNU parallel
功能可以使用序列化(per-command)输出的强大解决方案 ,并且可以并行执行 :
$ 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
脚本,它应该可移植到任何具有bash
Unix平台 。
注意:您可以在本Gist中找到在MIT许可证下发布的更为全面的实现。
如果将以下代码保存为脚本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[@]}"
另一种方法是,
eval `echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'`
输出:
a23 b23
不需要在这里创建一个子shell