我想将进程proc1的标准输出重定向到两个进程proc2和proc3:
proc2 -> stdout
/
proc1
\
proc3 -> stdout
我试过了
proc1 | (proc2 & proc3)
但它似乎不起作用,即
echo 123 | (tr 1 a & tr 1 b)
写
b23
而不是stdout
a23
b23
Answers:
编者按:
->(…)
是一个进程替换是一个非标准壳特征的一些POSIX兼容的外壳:bash
,ksh
,zsh
。
-这个答案不小心将通过管道输出过程中替换的输出也:echo 123 | tee >(tr 1 a) | tr 1 b
。
-进程替换的输出将以不可预测的方式交错,并且,除非在中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
命令。
tee
会stdout
在通过管道发送之前,将过程重定向的输出附加到其上,这与将同一实例的管道stdout
传递给多个命令明显不同。例如,@ dFecho 123 | tee >(tr 1 a) | tr 2 b
将导致1b3 ab3
,这对于原始问题而言毫无意义。
inproc | tee >(outproc1) >(outproc2) > /dev/null | outproc
。outproc将仅看到由outproc1和outproc2产生的输出。原始输入为“消失”。
就像dF所说的那样,bash
允许使用>(…)
运行命令的结构代替文件名。(还有一种<(…)
结构可以用另一个命令的输出代替文件名,但是现在已经不相关了,我只是为了完整性而提到它)。
如果您没有bash,或者在具有较旧版本bash的系统上运行,则可以通过使用FIFO文件来手动执行bash的操作。
实现所需目标的通用方法是:
subprocesses =“ abc d” mypid = $$ 对于$ subprocesses中的i#这样,我们与所有sh派生的shell兼容 做 mkfifo /tmp/pipe.$mypid.$i 完成
为我在$ subprocesses中 做 tr 1 $ i </tmp/pipe.$mypid.$i&#背景! 完成
proc1 | tee $(对于$ subprocesses中的i;执行echo /tmp/pipe.$mypid.$i;完成)
对于我在$ subprocesses中;做rm /tmp/pipe.$mypid.$i; 完成
注意:出于兼容性原因,我会$(…)
用反引号进行处理,但是我无法编写此答案(反引号在SO中使用)。通常,$(…)
即使足够老版本的ksh也可以使用,但如果不能,则将其…
用反引号引起来。
mkfifo
而不是mknod
,因为只有前者才符合POSIX。同样,使用无引号的命令替换很容易,并且有可能使用globing来提高效率。bash
在我的回答中,我自由地实施了一个更健壮的解决方案(尽管基于)。请注意,$(…)
它已经很长一段时间成为POSIX的一部分,因此我将远离难以预测的问题`…`
(因此,SO绝对允许使用`
in代码块甚至是内联代码范围(至少现在是:))。
bash
,)ksh
zsh
dF。的答案包含基于答案的种子,tee
以及可能会或可能不会有效的输出 过程替换
(>(...)
),具体取决于您的要求:
请注意,进程替换是一种非标准功能,(大多数情况下)不支持仅POSIX功能的shell,例如dash
(/bin/sh
在Ubuntu上起作用)。Shell脚本目标应该不依靠他们。/bin/sh
echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
这种方法的陷阱是:
不可预测的异步输出行为:输出过程替换中命令的输出流>(...)
以不可预测的方式交错。
在bash
和ksh
(相对于zsh
-,但请参见下面的例外):
bash
和ksh
不不等待输出过程替换生成的进程到结束,至少在默认情况下。 请注意,从内部启动的命令
>(...)
与原始Shell无关,并且您不容易确定它们何时完成;在tee
书写后一切都会结束,但取代的过程仍然会消耗从内核和文件I / O缓冲区的各种数据,以及任何时间采取的数据的内部处理。如果您的外壳随后继续依赖子流程生成的任何内容,则可能会遇到竞争状况。
zsh
是唯一的外壳确实默认等待的过程中的输出处理替换运行到完成,除了如果是标准错误被重定向到一个(2> >(...)
)。
ksh
(至少从版本开始93u+
)(允许使用无参数)wait
等待输出过程替换生成的过程完成。
请注意,在交互式会话中,这也可能导致等待任何待处理的后台作业。
bash v4.4+
可以等待最近有推出输出进程替换wait $!
,但争论少wait
做不工作,使这个不适合用命令多输出过程换人。
然而,bash
和ksh
可强制等待通过管道命令| cat
,但请注意,这使得在运行命令的子shell。注意事项:
ksh
(截至ksh 93u+
)不支持将stderr发送到输出进程替代(2> >(...)
);这种尝试被默默地忽略了。
尽管默认情况下(与(更常见的)stdout输出过程替换zsh
同步)是(值得称赞的),但是即使该技术也无法使它们与stderr输出过程替换同步()。| cat
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
解决方案是以下解决方案,但是,对于大型输入集,这是缓慢的,因为壳循环本质上是缓慢的。
还要注意,这会交替输出目标命令的输出行。
while IFS= read -r line; do
tr 1 a <<<"$line"
tr 1 b <<<"$line"
done < <(echo '123')
安装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
脚本,因此应可移植到任何具有bash
POSIX的Unix平台上。
注意:您可以在此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[@]}"
自@dF:提到PowerShell已经开球以来,我想我将展示一种在PowerShell中执行此操作的方法。
PS > "123" | % {
$_.Replace( "1", "a"),
$_.Replace( "2", "b" )
}
a23
1b3
请注意,在创建下一个对象之前,将处理从第一个命令发出的每个对象。这可以允许缩放到非常大的输入。
while IFS= read -r line; do tr 1 a <<<"$line"; tr 1 b <<<"$line"; done < <(echo '123')
于在Bash中进行,在内存方面可以很好地扩展,而性能方面则可以。
您还可以将输出保存在变量中,并将其用于其他过程:
out=$(proc1); echo "$out" | proc2; echo "$out" | proc3
但是,只有在
proc1
在某点终止:-)proc1
不会产生太多输出(不知道有什么限制,但可能是您的RAM)但是很容易记住,并且可以从在此生成的进程获得的输出上有更多选择,例如:
out=$(proc1); echo $(echo "$out" | proc2) / $(echo "$out" | proc3) | bc
我在做这样的事情时遇到了困难 | tee >(proc2) >(proc3) >/dev/null
方法。
另一种方法是
eval `echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'`
输出:
a23
b23
无需在此处创建子外壳
echo 123 |{tr 1 a,tr 1 b}
抱怨{tr
不存在,如果您放入多余的空格,则会因为逗号而等待额外的输入,并且如果您将逗号更改为分号或与号,则只会打印出第一个-不能同时打印。
bash
,ksh
,zsh
)通过创建命令行echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'
的字符串,然后使该字符串eval
。就是说,它(a)在构造字符串的过程中创建了3个子shell(1表示`...`
嵌入管道,2表示嵌入管道的段,并且(b)更重要的是,它复制了输入命令,从而得到了一个单独的副本为每个目标tr
命令运行。除了效率低下外,同一命令运行两次不一定会产生相同的输出两次