当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《如何在Unix(或Windows)中使用(最好是未命名的)管道将一个进程的stdout发送到多个进程?》经验,为你挑选了4个好方法。

我想将进程proc1的stdout重定向到两个进程proc2和proc3:

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

我试过了

 proc1 | (proc2 & proc3)

但它似乎没有用,即

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

 b23

stdout而不是

 a23
 b23

dF... 121

编者按:
- >(…)是一个进程替换是一个非标准壳特征一些 POSIX兼容的外壳:bash,ksh,zsh.
-这个答案不小心将通过管道输出过程中替换的输出: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下,我认为内置shell没有等价物.微软的Windows PowerShelltee虽然有一个命令.



1> dF...:

编者按:
- >(…)是一个进程替换是一个非标准壳特征一些 POSIX兼容的外壳:bash,ksh,zsh.
-这个答案不小心将通过管道输出过程中替换的输出: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下,我认为内置shell没有等价物.微软的Windows PowerShelltee虽然有一个命令.


这并不完全符合@secr的要求.`tee`会在通过管道发送之前将进程重定向的输出附加到`stdout`,这与将`stdout`的同一实例连接到多个命令明显不同.@dF,例如,`echo 123 | tee>(tr 1 a)| tr 2 b`将导致`1b3 ab3`,这在原始问题的背景下没有任何意义.
虽然非常方便,但要注意在>(...)内部启动的命令与原始shell分离,并且您无法轻易确定它们何时完成; _tee_将在写完所有内容后完成,但替换进程仍将消耗内核和文件I/O中各种缓冲区的数据,以及内部数据处理所花费的时间.如果你的外壳继续依赖子进程产生的任何东西,你可能会遇到竞争条件.
这不是POSIX构造,需要bash或ksh.你对tcsh和破折号等运气不好
@pixelbeat:...但它可以分解为POSIX结构(参见我的回答:)
@Dejay Clayton:您可以使用`inproc |来丢弃原始输入 tee>(outproc1)>(outproc2)>/dev/null | outproc`.outproc只能看到outproc1和outproc2产生的输出.原始输入"消失了".

2> tzot..:

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

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

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

决定有多少进程应该接收命令的输出,并创建尽可能多的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

启动所有子进程等待来自FIFO的输入:

    for i in $subprocesses
    do
        tr 1 $i 

执行你的命令发球到FIFO:

    proc1 | tee $(for i in $subprocesses; do echo /tmp/pipe.$mypid.$i; done)

最后,删除FIFO:

    for i in $subprocesses; do rm /tmp/pipe.$mypid.$i; done

注意:出于兼容性原因,我会$(…)使用反引号,但我不能写这个答案(反引用在SO中使用).通常情况下,$(…)即使在旧版本的ksh中也足够大,但如果没有,则将该部分包含在反引号中.



3> mklement0..:

UNIX( ,bash,)kshzsh

dF.的答案包含基于和输出过程替换 ()的答案的种子,根据您的要求可能会或可能不会起作用:tee
>(...)

请注意,进程替换是一种非标准功能,(大多数情况下)仅限POSIX功能的shell(例如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默认情况下,唯一的shell 等待输出流程替换中的进程完成,除非它是重定向到one()的stderr2> >(...).

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

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

但是,bash并且ksh可以通过管道命令来强制等待| cat,但请注意,这使命令在子shell中运行.警告:

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

虽然zsh(值得称道)默认情况下与(更常见的)stdout输出流程替换| cat同步,但即使该技术也不能使它们与stderr输出流程替换(2> >(...))同步.

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

以下命令在bashor中运行时ksh,说明了有问题的行为(您可能需要多次运行才能看到这两种症状):AFTER通常会在输出替换输出之前打印,并且后者的输出可以不可预测地交错.

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

简而言之:

保证特定的每命令输出序列:

无论是bash也不是ksh也不zsh支持.

同步执行:

可行,除了使用stderr -sourced输出流程替换:

zsh,他们总是异步.

ksh,他们根本不工作.

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


请注意,tzot更加繁琐,但可能符合POSIX标准的解决方案也会出现不可预测的输出行为 ; 但是,通过使用,wait您可以确保后续命令在所有后台进程完成后才开始执行.
请参阅底部,了解更强大,同步,串行化的输出实现.


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

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许可下发布的更完整的实现.

如果您将下面的代码保存为脚本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[@]}"



4> Jay Bazuzi..:

由于@dF:提到PowerShell有T恤,我想我会在PowerShell中展示一种方法.

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

a23
1b3

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

推荐阅读
爱唱歌的郭少文_
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有