OS X / Linux:如何将输出同时传递给两个进程?

27

我知道

program1 | program2

并且

program1 | tee outputfile | program2

但是是否有一种方法可以将程序1的输出输入到程序2和程序3中?


我正在处理我的回答……由于我只是Debian用户,欢迎提供其他操作系统(特别是MaxOS)的反馈! - F. Hauri - Give Up GitHub
6个回答

35
你可以使用tee和进程替代来实现这个功能。
program1 | tee >(program2) >(program3)

program1的输出将被连接到括号中的内容,即program2program3


14
tee 的输出复制到每个文件 以及标准输出,并且由于 >() 会被视为 tee文件,因此你需要编写:prg1 | tee >(prg2) > >(prg3),以避免将 prg1 的输出转储到控制台(请注意附加的 >)。 - F. Hauri - Give Up GitHub
2
@StephenNiedzielski 注意:在这种情况下,prg3将作为stdin读取,其输出将与prg2的输出连接到prg1的输出后面!! - F. Hauri - Give Up GitHub
我添加了一个带有可工作演示的答案。https://dev59.com/e2kv5IYBdhLWcg3w_Vmn#48374970 - Bruno Bronosky
1
@AkshayArora 这是基本的重定向 > file 将 STDOUT 重定向到文件。它也可以写成:1>file。在这种情况下:prg1 | tee >(prg2) 1> >(prg3) - F. Hauri - Give Up GitHub
1
@AkshayArora 你可以在安静地阅读我的答案中找到更多示例。 - F. Hauri - Give Up GitHub
显示剩余6条评论

26

并行化介绍

这个看起来很简单,但这不仅是可能的,而且这样做会生成并发或同时执行的进程。

您可能需要注意一些特定的影响,比如执行顺序、执行时间等等。

本文末尾有一些示例。

首先给出兼容的答案

由于此问题被标记为,我将首先给出一个POSIX兼容的答案。(对于

是的,有一种使用未命名管道的方法。

在此示例中,我将生成100,000个数字范围,并随机化它们,然后使用4个不同的压缩工具压缩结果以比较压缩比...

为了做到这一点,我将首先运行准备工作:

GZIP_CMD=`which gzip`
BZIP2_CMD=`which bzip2`
LZMA_CMD=`which lzma`
XZ_CMD=`which xz`
MD5SUM_CMD=`which md5sum`
SED_CMD=`which sed`

注意:指定完整的命令路径可以防止一些 shell 解释器(例如 busybox)运行内置压缩器。这样做还将确保相同的语法在不同操作系统的安装上独立运行(路径可能在 MacOS、Ubuntu、RedHat、HP-Ux等之间不同)。

语法NN>&1(其中 NN 是 3 到 63 之间的数字)会生成一个 无名管道,可以在 /dev/fd/NN 找到它。(文件描述符 0 到 2 已经分别打开为 0: STDIN、1: STDOUT 和 2: STDERR)。

尝试这个命令(已在 下测试):

(((( seq 1 100000 | shuf | tee /dev/fd/4 /dev/fd/5 /dev/fd/6 /dev/fd/7 | $GZIP_CMD >/tmp/tst.gz ) 4>&1 | $BZIP2_CMD >/tmp/tst.bz2 ) 5>&1 | $LZMA_CMD >/tmp/tst.lzma ) 6>&1 | $XZ_CMD >/tmp/tst.xz ) 7>&1 | $MD5SUM_CMD

更易读:

GZIP_CMD=`which gzip`
BZIP2_CMD=`which bzip2`
LZMA_CMD=`which lzma`
XZ_CMD=`which xz`
MD5SUM_CMD=`which md5sum`

(
  (
    (
      (
        seq 1 100000 |
          shuf |
          tee /dev/fd/4 /dev/fd/5 /dev/fd/6 /dev/fd/7 |
          $GZIP_CMD >/tmp/tst.gz
      ) 4>&1 |
        $BZIP2_CMD >/tmp/tst.bz2
    ) 5>&1 |
      $LZMA_CMD >/tmp/tst.lzma
  ) 6>&1 |
    $XZ_CMD >/tmp/tst.xz
) 7>&1 |
  $MD5SUM_CMD
2e67f6ad33745dc5134767f0954cbdd6  -

由于shuf进行随机排列,因此如果您尝试这样做,则必须获得不同的结果。

ls -ltrS /tmp/tst.*
-rw-r--r-- 1 user user 230516 oct  1 22:14 /tmp/tst.bz2
-rw-r--r-- 1 user user 254811 oct  1 22:14 /tmp/tst.lzma
-rw-r--r-- 1 user user 254892 oct  1 22:14 /tmp/tst.xz
-rw-r--r-- 1 user user 275003 oct  1 22:14 /tmp/tst.gz

但是你必须能够比较 MD5 校验和:

SED_CMD=`which sed`

for chk in gz:$GZIP_CMD bz2:$BZIP2_CMD lzma:$LZMA_CMD xz:$XZ_CMD;do
    ${chk#*:} -d < /tmp/tst.${chk%:*} |
        $MD5SUM_CMD |
        $SED_CMD s/-$/tst.${chk%:*}/
  done
2e67f6ad33745dc5134767f0954cbdd6  tst.gz
2e67f6ad33745dc5134767f0954cbdd6  tst.bz2
2e67f6ad33745dc5134767f0954cbdd6  tst.lzma
2e67f6ad33745dc5134767f0954cbdd6  tst.xz

使用 特性

使用一些 bashims,这样看起来会更好,例如使用 /dev/fd/{4,5,6,7},而不是 tee /dev/fd/4 /dev/fd/5 /...

(((( seq 1 100000 | shuf | tee /dev/fd/{4,5,6,7} | gzip >/tmp/tst.gz ) 4>&1 |
   bzip2 >/tmp/tst.bz2 ) 5>&1 | lzma >/tmp/tst.lzma ) 6>&1 |
   xz >/tmp/tst.xz ) 7>&1 | md5sum
29078875555e113b31bd1ae876937d4b  -

将产生相同的结果。

最终检查

这不会创建任何文件,但可以让您比较排序整数范围的压缩大小,在4种不同的压缩工具之间进行比较(为了好玩,我使用了4种不同的格式化输出方法):

(
  (
    (
      (
        (
          seq 1 100000 |
            tee /dev/fd/{4,5,6,7} |
              gzip |
              wc -c |
              sed s/^/gzip:\ \ / >&3
        ) 4>&1 |
          bzip2 |
          wc -c |
          xargs printf "bzip2: %s\n" >&3
      ) 5>&1 |
        lzma |
        wc -c |
        perl -pe 's/^/lzma:   /' >&3
    ) 6>&1 |
      xz |
      wc -c |
      awk '{printf "xz: %9s\n",$1}' >&3
  ) 7>&1 |
    wc -c
) 3>&1
gzip:  215157
bzip2: 124009
lzma:   17948
xz:     17992
588895

本示例演示如何在子shell中重定向stdinstdout,并将它们合并后用于最终输出。

>(...)<(...)语法

最近的版本允许一种新的语法特性。

seq 1 100000 | wc -l
100000

seq 1 100000 > >( wc -l )
100000

wc -l < <( seq 1 100000 )
100000

<()语法会生成一个临时的无名管道,其他文件描述符/dev/fd/XX可以利用这个无名管道。

md5sum <(zcat /tmp/tst.gz) <(bzcat /tmp/tst.bz2) <(
         lzcat /tmp/tst.lzma) <(xzcat /tmp/tst.xz)
29078875555e113b31bd1ae876937d4b  /dev/fd/63
29078875555e113b31bd1ae876937d4b  /dev/fd/62
29078875555e113b31bd1ae876937d4b  /dev/fd/61
29078875555e113b31bd1ae876937d4b  /dev/fd/60

更复杂的演示

这需要安装GNU file实用程序。将根据扩展名或文件类型确定要运行的命令。

for file in /tmp/tst.*;do
    cmd=$(which ${file##*.}) || {
        cmd=$(file -b --mime-type $file)
        cmd=$(which ${cmd#*-})
    }
    read -a md5 < <($cmd -d <$file|md5sum)
    echo $md5 \ $file
  done
29078875555e113b31bd1ae876937d4b  /tmp/tst.bz2
29078875555e113b31bd1ae876937d4b  /tmp/tst.gz
29078875555e113b31bd1ae876937d4b  /tmp/tst.lzma
29078875555e113b31bd1ae876937d4b  /tmp/tst.xz

这使您可以按照以下语法执行与以前相同的操作:

seq 1 100000 |
    shuf |
        tee >(
            echo gzip. $( gzip | wc -c )
          )  >(
            echo gzip, $( wc -c < <(gzip))
          ) >(
            gzip  | wc -c | sed s/^/gzip:\ \ /
          ) >(
            bzip2 | wc -c | xargs printf "bzip2: %s\n"
          ) >(
            lzma  | wc -c | perl -pe 's/^/lzma:  /'
          ) >(
            xz    | wc -c | awk '{printf "xz: %9s\n",$1}'
          ) > >(
            echo raw: $(wc -c)
          ) |
        xargs printf "%-8s %9d\n"

raw:        588895
xz:         254556
lzma:       254472
bzip2:      231111
gzip:       274867
gzip,       274867
gzip.       274867

注意:我使用了不同的方式来计算gzip压缩次数。

注意:由于这个操作是同时进行的,输出顺序将取决于每个命令所需的时间。

进一步谈论并行化

如果您运行一些多核或多处理器计算机,请尝试进行比较:

i=1
time for file in /tmp/tst.*;do
    cmd=$(which ${file##*.}) || {
        cmd=$(file -b --mime-type $file)
        cmd=$(which ${cmd#*-})
    }
    read -a md5 < <($cmd -d <$file|md5sum)
    echo $((i++)) $md5 \ $file
  done |
cat -n

可能会呈现出:

     1      1 29078875555e113b31bd1ae876937d4b  /tmp/tst.bz2
     2      2 29078875555e113b31bd1ae876937d4b  /tmp/tst.gz
     3      3 29078875555e113b31bd1ae876937d4b  /tmp/tst.lzma
     4      4 29078875555e113b31bd1ae876937d4b  /tmp/tst.xz

real    0m0.101s

使用这个:

time  (
    i=1 pids=()
    for file in /tmp/tst.*;do
        cmd=$(which ${file##*.}) || {
            cmd=$(file -b --mime-type $file)
            cmd=$(which ${cmd#*-})
        }
        (
             read -a md5 < <($cmd -d <$file|md5sum)
             echo $i $md5 \ $file
        ) & pids+=($!)
      ((i++))
      done
    wait ${pids[@]}
) |
cat -n

可以提供:

     1      2 29078875555e113b31bd1ae876937d4b  /tmp/tst.gz
     2      1 29078875555e113b31bd1ae876937d4b  /tmp/tst.bz2
     3      4 29078875555e113b31bd1ae876937d4b  /tmp/tst.xz
     4      3 29078875555e113b31bd1ae876937d4b  /tmp/tst.lzma

real    0m0.070s

其中的排序取决于每个fork使用的类型。


进一步阅读:parallel.sh 使用bash并行化流处理 - F. Hauri - Give Up GitHub

2

其他答案介绍了概念。这里是一个实际演示:

$ echo "Leeroy Jenkins" | tee >(md5sum > out1) >(sha1sum > out2) > out3

$ cat out1
11e001d91e4badcff8fe22aea05a7458  -

$ echo "Leeroy Jenkins" | md5sum
11e001d91e4badcff8fe22aea05a7458  -

$ cat out2
5ed25619ce04b421fab94f57438d6502c66851c1  -

$ echo "Leeroy Jenkins" | sha1sum
5ed25619ce04b421fab94f57438d6502c66851c1  -

$ cat out3
Leeroy Jenkins

当然,你可以使用> /dev/null代替out3。

如果子命令输出将被重定向,语法“> /dev/null”是无用的!更简单的方法是:tee <<<"Adam's familly" >(md5sum > out1) | sha1sum > out2 - F. Hauri - Give Up GitHub

2

bash手册提到它使用命名管道或命名文件描述符来模拟>(...)语法,因此如果您不想依赖bash,也可以在脚本中手动执行该操作。

mknod FIFO
program3 < FIFO &
program1 | tee FIFO | program2
wait
rm FIFO

1

您可以尝试将程序1的输出保存到文件中,然后将其提供给程序2和程序3的输入。

program1 > temp; program2 < temp; program3 < temp;

6
好的,我明白了。以下是翻译的结果:是的,我了解(program1 | tee outputfile | program2; program3 <outputfile),但如果可以轻松避免最好不要用它。 - Jason S

-1

使用(;)语法... 尝试ps aux | (head -n 1; tail -n 1)


这并没有回答问题:没有两个进程,只有一个进程,完成两个连续的操作...而且:这在小系统上不起作用;尝试使用 seq 1 30 | (head ; tail)!这不能按预期工作! - F. Hauri - Give Up GitHub

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接