如何将stdout发送到多个命令?


186

我认为有些命令会过滤或作用于输入,然后将其作为输出传递给我stdout-但是有些命令只会接受stdin和并对其执行任何操作,而不会输出任何内容。

我对OS X最熟悉,因此立即想到的是pbcopypbpaste-这是两种访问系统剪贴板的方法。

无论如何,我知道如果我要输出stdout并把输出吐到两个stdout文件中,则可以使用tee命令。而且我对有所了解xargs,但我认为这不是我想要的。

我想知道如何stdout在两个(或多个)命令之间进行拆分。例如:

cat file.txt | stdout-split -c1 pbcopy -c2 grep -i errors

可能有比该示例更好的示例,但是我真的很想知道如何将stdout发送到不中继该命令的命令,同时又避免stdout被“静音”-我不是在问如何对cat文件和grep部分并将其复制到剪贴板-具体命令不是那么重要。

另外-我不是在问如何将其发送到文件,并且stdout-这可能是一个“重复”的问题(对不起),但是我进行了一些查找,只能找到正在询问如何在stdout和文件之间进行拆分的类似问题。 -这些问题的答案似乎是tee,我认为这对我不起作用。

最后,您可能会问:“为什么不使pbcopy成为管道链中的最后一件事?” 我的回答是1)如果我想使用它并仍然在控制台中看到输出怎么办?2)如果我要使用两个stdout在处理输入后不输出的命令怎么办?

哦,还有一件事-我意识到我可以使用tee一个命名管道(mkfifo),但是我希望可以通过一种简洁的方式内联地完成此操作,而无需事先设置:)


Answers:


239

您可以tee为此使用并处理替代:

cat file.txt | tee >(pbcopy) | grep errors

这将发送的所有输出cat file.txtpbcopy,你只会得到的结果,grep您的控制台上。

您可以在tee零件中放置多个过程:

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors

21
无关紧要pbcopy,但通常值得一提:在原始输入之后的下一个管道段中,也可以看到任何过程替代输出。例如:seq 3 | tee >(cat -n) | cat -ecat -n数字输入线,cat -e标记与换行$;你会看到一个cat -e被施加到原始输入(第一)和(然后)的从输出cat -n)。多个过程替换的输出将以不确定的顺序到达。
mklement0

49
>(只能在bash。如果您尝试使用例如sh它将无法正常工作。发出此通知很重要。
AAlvz 2014年

10
@AAlvz:好点:进程替换不是 POSIX功能;dash,它sh在Ubuntu 上的行为不支持它,甚至Bash本身在激活时sh或激活时都会停用该功能set -o posix。但是,不仅Bash支持流程替代:ksh而且还zsh支持它们(不确定其他替代品)。
mklement0

2
@ mklement0似乎不正确。在zsh(Ubuntu 14.04)上,行打印为:1 1 2 2 3 3 1 $ 2 $ 3 $令人遗憾的是,因为我真的希望功能能够像您所说的那样。
阿克套

2
@Aktau:的确,我的示例命令仅按所述工作,bash并且ksh- zsh显然不会通过管道发送来自输出过程替代的输出(可以说,这是更好的选择,因为它不会污染发送给下一个管道段的内容-尽管它仍然会打印)。但是,在所有提到的Shell中,最好不要使用单个管道将常规的stdout输出和来自进程替换的输出混合在一起-输出顺序将是不可预测的,其方式可能只是很少出现或以较大的方式出现输出数据集。
mklement0

124

您可以为指定多个文件名tee,此外,标准输出可以通过管道传递到一个命令中。要将输出分派到多个命令,您需要创建多个管道并将每个管道指定为的一个输出tee。有几种方法可以做到这一点。

流程替代

如果您的shell是ksh93,bash或zsh,则可以使用进程替换。这是将管道传递给需要文件名的命令的一种方法。外壳程序创建管道,并将类似的文件名传递/dev/fd/3给命令。该数字是管道连接到的文件描述符。一些Unix变体不支持/dev/fd; 在这些文件上,使用命名管道代替(请参见下文)。

tee >(command1) >(command2) | command3

文件描述符

在任何POSIX Shell中,都可以显式使用多个文件描述符。这需要支持unix的unix变体/dev/fd,因为除其中一个输出外,所有输出都tee必须通过名称指定。

{ { { tee /dev/fd/3 /dev/fd/4 | command1 >&9;
    } 3>&1 | command2 >&9;
  } 4>&1 | command3 >&9;
} 9>&1

命名管道

最基本和可移植的方法是使用命名管道。缺点是您需要找到一个可写目录,创建管道,然后进行清理。

tmp_dir=$(mktemp -d)
mkfifo "$tmp_dir/f1" "$tmp_dir/f2"
command1 <"$tmp_dir/f1" & pid1=$!
command2 <"$tmp_dir/f2" & pid2=$!
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3
rm -rf "$tmp_dir"
wait $pid1 $pid2

10
非常感谢为不想依赖bash或特定ksh的用户提供了两个替代版本。
trr

tee "$tmp_dir/f1" "$tmp_dir/f2" | command3应该肯定是command3 | tee "$tmp_dir/f1" "$tmp_dir/f2",只要你想标准输出的command3管道来tee,不是吗?我在下测试了您的版本,dashtee无限期地等待输入,但是切换顺序产生了预期的结果。
AdrianGünter'18

1
@AdrianGünter所有号三个例子从标准输入读取数据并将其发送到每个commandcommand2command3
吉尔斯

我看到了@Gilles,我误解了意图并试图错误地使用该代码段。感谢您的澄清!
AdrianGünter'18

如果您无法控制所使用的shell,但是可以显式使用bash,则可以这样做<command> | bash -c 'tee >(command1) >(command2) | command3'。就我而言,这很有帮助。
gc5

16

只是玩过程替代。

mycommand_exec |tee >(grep ook > ook.txt) >(grep eek > eek.txt)

grep是两个二进制文件,这些二进制文件的输出mycommand_exec与其特定于进程的输入相同。


16

如果您正在使用,zsh则可以利用MULTIOS功能的强大功能,即tee完全摆脱命令:

uname >file1 >file2

只会将的输出写入uname两个不同的文件:file1和中file2,等效于uname | tee file1 >file2

类似地重定向标准输入

wc -l <file1 <file2

等价于cat file1 file2 | wc -l(请注意,这与并不相同wc -l file1 file2,后者分别计算每个文件中的行数)。

当然,您也可以MULTIOS使用进程替换将输出重定向到文件而不是其他进程,例如:

echo abc > >(grep -o a) > >(tr b x) > >(sed 's/c/y/')

3
很高兴知道。MULTIOS是默认情况下为ON 的选项(可以使用进行关闭unsetopt MULTIOS)。
mklement0

6

对于命令产生的较小输出,我们可以将输出重定向到临时文件,然后将这些临时文件循环发送到命令。当执行命令的顺序可能很重要时,这很有用。

例如,以下脚本可以做到这一点:

#!/bin/sh

temp=$( mktemp )
cat /dev/stdin > "$temp"

for arg
do
    eval "$arg" < "$temp"
done
rm "$temp"

/bin/sh作为dash外壳程序在Ubuntu 16.04上测试运行:

$ cat /etc/passwd | ./multiple_pipes.sh  'wc -l'  'grep "root"'                                                          
48
root:x:0:0:root:/root:/bin/bash

5

将命令捕获STDOUT到变量中,然后根据需要多次重复使用它:

commandoutput="$(command-to-run)"
echo "$commandoutput" | grep -i errors
echo "$commandoutput" | pbcopy

如果您也需要捕获STDERR,请2>&1在命令末尾使用,如下所示:

commandoutput="$(command-to-run 2>&1)"

3
变量存储在哪里?如果您要处理的是大文件或类似文件,这是否会占用大量内存?变量的大小是否受到限制?
cwd 2012年

1
如果$ commandoutput很大怎么办?最好使用管道和进程替换。
Nikhil Mulley 2012年

4
显然,只有当您知道输出的大小很容易容纳在内存中并且可以在运行下一个命令之前缓冲整个输出时,才可以使用此解决方案。管道通过允许任意长度的数据并将其实时流传输到接收器来解决这两个问题。
trr 2013年

2
如果您的输出很小,并且知道输出将是文本而不是二进制,那么这是一个很好的解决方案。(shell变量通常不是二进制安全的)
Rucent88

1
我无法使用它来处理二进制数据。我认为回声试图解释空字节或其他一些非字符数据。
罗尔夫


0

这是一个肮脏的局部解决方案,它与包括在内的任何shell兼容busybox

它解决的更狭窄的问题是:将整个文件打印stdout到一个控制台上,然后在另一个控制台上进行过滤,而没有临时文件或命名管道。

  • 启动到同一主机的另一个会话。要查找其TTY名称,请键入tty。假设/dev/pty/2
  • 在第一个会话中,运行 the_program | tee /dev/pty/2 | grep ImportantLog:

您将获得一个完整的日志和一个经过过滤的日志。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.