如何将标准输出发送到多个命令?

cwd*_*cwd 245 command-line shell io-redirection

有一些命令可以过滤或作用于输入,然后将其作为输出传递,我认为通常是stdout- 但有些命令只会接受stdin并执行它们对它所做的任何事情,并且不输出任何内容。

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

无论如何,我知道如果我想使用 stdout 并将输出输出到两个stdout文件和一个文件,那么我可以使用该tee命令。我对 有一点了解xargs,但我认为这不是我要找的。

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

cat file.txt | stdout-split -c1 pbcopy -c2 grep -i errors
Run Code Online (Sandbox Code Playgroud)

可能有一个比那个更好的例子,但我真的很想知道如何将标准输出发送到一个不中继它的命令,同时避免stdout被“静音” - 我不是问如何cat归档和grep它的一部分并将其复制到剪贴板 - 具体命令并不那么重要。

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

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

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

Mat*_*Mat 298

您可以tee为此使用和处理替换:

cat file.txt | tee >(pbcopy) | grep errors
Run Code Online (Sandbox Code Playgroud)

这将发送cat file.txtto 的所有输出pbcopy,并且您只能grep在控制台上获得结果。

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

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors
Run Code Online (Sandbox Code Playgroud)

  • `>(` 仅在 `bash` 中有效。如果您尝试使用例如 `sh`,它将不起作用。重要的是要注意这一点。 (66认同)
  • 与 `pbcopy` 无关,但总体上值得一提:无论进程替换输出_也_被下一个管道段看到,在原始输入之后;例如:`seq 3 | 三通 >(cat -n) | cat -e`(`cat -n` 给输入行编号,`cat -e` 用`$` 标记换行符;你会看到`cat -e` 应用于原始输入(第一个)和(然后) `cat -n` 的输出)。_multiple_ 进程替换的输出将以不确定的顺序到达。 (31认同)
  • @AAlvz:好点:进程替换_不是_POSIX 功能;`dash` 在 Ubuntu 上充当 `sh`,不支持它,甚至 Bash 本身在作为 `sh` 调用或当 `set -o posix` 生效时也会停用该功能。然而,不仅仅是 Bash 支持进程替换:`ksh` 和 `zsh` 也支持它们(不确定其他的)。 (12认同)
  • @mklement0 这似乎不是真的。在 zsh (Ubuntu 14.04) 上,您的行打印: 1 1 2 2 3 3 1$ 2$ 3$ 这很可悲,因为我真的希望功能如您所说。 (2认同)
  • @Aktau:确实,我的示例命令仅按照 `bash` 和 `ksh` 中的描述工作 - `zsh` 显然不会通过管道发送来自输出进程替换的输出(可以说,这是 _preferable_,因为它不会污染什么被发送到下一个管道段 - 尽管它仍然_打印_)。然而,在提到的 _all_ shell 中,拥有一个单一的管道通常不是一个好主意,其中常规 stdout 输出和来自进程替换的输出混合在一起 - 输出顺序将无法预测,其方式可能很少或大输出数据集。 (2认同)
  • “tee”技巧有效(谢谢!)。但它也将所有内容发送到标准输出,我将大量垃圾打印到控制台,因此我在命令末尾添加了“2&>/dev/null”。 (2认同)

Gil*_*il' 158

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

过程替换

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

tee >(command1) >(command2) | command3
Run Code Online (Sandbox Code Playgroud)

文件描述符

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

{ { { tee /dev/fd/3 /dev/fd/4 | command1 >&9;
    } 3>&1 | command2 >&9;
  } 4>&1 | command3 >&9;
} 9>&1
Run Code Online (Sandbox Code Playgroud)

命名管道

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

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
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢为那些不想依赖 bash 或某个 ksh 的人提供两个替代版本。 (11认同)
  • @AdrianGünter 否。所有三个示例都从标准输入读取数据并将其发送到每个“command”、“command2”和“command3”。 (3认同)

jim*_*mij 23

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

uname >file1 >file2
Run Code Online (Sandbox Code Playgroud)

只会将 的输出写入uname两个不同的文件:file1file2,相当于uname | tee file1 >file2

同样重定向标准输入

wc -l <file1 <file2
Run Code Online (Sandbox Code Playgroud)

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

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

echo abc > >(grep -o a) > >(tr b x) > >(sed 's/c/y/')
Run Code Online (Sandbox Code Playgroud)

  • 很高兴知道。`MULTIOS` 是一个默认开启的 _option_(并且可以使用 `unsetopt MULTIOS` 关闭)。 (5认同)

Nik*_*ley 19

只是玩过程替换。

mycommand_exec |tee >(grep ook > ook.txt) >(grep eek > eek.txt)
Run Code Online (Sandbox Code Playgroud)

grep是两个二进制文件,它们的输出mycommand_exec与其过程特定的输入相同。


Xor*_*rax 11

还有pee来自moreutils包的。它是为此而设计的:

pee 'command1' 'command2' 'cat -'
Run Code Online (Sandbox Code Playgroud)


Ser*_*nyy 7

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

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

#!/bin/sh

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

for arg
do
    eval "$arg" < "$temp"
done
rm "$temp"
Run Code Online (Sandbox Code Playgroud)

在 Ubuntu 16.04 上使用/bin/shas dashshell测试运行:

$ cat /etc/passwd | ./multiple_pipes.sh  'wc -l'  'grep "root"'                                                          
48
root:x:0:0:root:/root:/bin/bash
Run Code Online (Sandbox Code Playgroud)


lae*_*ade 5

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

commandoutput="$(command-to-run)"
echo "$commandoutput" | grep -i errors
echo "$commandoutput" | pbcopy
Run Code Online (Sandbox Code Playgroud)

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

commandoutput="$(command-to-run 2>&1)"
Run Code Online (Sandbox Code Playgroud)

  • 变量存储在哪里?如果你正在处理一个大文件或类似的东西,这会不会占用大量内存?变量有大小限制吗? (4认同)
  • 显然,只有当您知道输出的大小很容易适合内存时,此解决方案才有可能,并且您可以在运行下一个命令之前缓冲整个输出。管道通过允许任意长度的数据并在其生成时将其实时流式传输到接收器来解决这两个问题。 (4认同)
  • 如果您的输出很小,并且您知道输出将是文本而不是二进制,那么这是一个很好的解决方案。(shell 变量通常不是二进制安全的) (2认同)