进程替换为grep缺少预期输出

vda*_*vid 5 bash pipeline io-redirection process-substitution

假设我有一个程序输出:

abcd
l33t
1234
Run Code Online (Sandbox Code Playgroud)

我会用它来模拟printf 'abcd\nl33t\n1234\n'.我想同时将这个输出提供给两个程序.我的想法是使用进程替换tee.假设我想将输出的副本提供给grep:

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]' >&2) | grep '[0-9]'
Run Code Online (Sandbox Code Playgroud)

我使用Bash 4.1.2(Linux,CentOS 6.5)得到以下内容,这很好:

l33t
1234
abcd
l33t
Run Code Online (Sandbox Code Playgroud)

但是如果进程替换没有被重定向到stderr(即没有>&2),就像这样:

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]') | grep '[0-9]'
Run Code Online (Sandbox Code Playgroud)

然后我得到:

l33t
1234
l33t
Run Code Online (Sandbox Code Playgroud)

这就像进程替换的stdout(第一个grep)被管道(第二个grep)之后的进程使用.除了第二个grep已经自己读取东西,所以我想它不应该考虑第一个grep中的东西.除非我弄错了(我肯定是).

我错过了什么?

cxw*_*cxw 3

解释

就命令行而言,进程替换只是创建特殊文件名的一种方法。(另请参阅文档。)因此第二个管道实际上看起来像:

printf 'abcd\nl33t\n1234\n' | tee /dev/fd/nn | grep '[0-9]'
Run Code Online (Sandbox Code Playgroud)

其中nn是一些文件描述符编号。的完整输出printf转到/dev/fd/nn,并且也转到grep '[0-9]'。因此,仅打印数值。

至于 内部的进程>(),它继承了其父进程的 stdout。在这种情况下,该标准输出位于管道内部。因此, 的输出grep '[a-z]'就像 的标准输出一样通过管道tee。因此,整个管道仅传递包含数字的行。

当您改为写入 stderr ( >&2) 时,您将绕过最后一个管道阶段。因此,on stderr 的输出grep '[a-z]'将发送到终端。

修复

要在不使用 stderr 的情况下解决此问题,您可以为屏幕使用另一个别名。例如:

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]' >/dev/tty ) | grep '[0-9]'
                                               # ^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

这给了我输出

l33t
1234
abcd
l33t
Run Code Online (Sandbox Code Playgroud)

测试这个

为了解决这个问题,我跑了echo >(ps)。该进程是运行管道的进程 ps的子进程。bash

我也跑了

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]')
Run Code Online (Sandbox Code Playgroud)

没有| grep '[0-9]'最后。在我的系统上,我看到

abcd    <--- the output of the tee
l33t         ditto
1234         ditto
abcd    <--  the output of the grep '[a-z]'
l33t         ditto
Run Code Online (Sandbox Code Playgroud)

所有五行都进入grep '[0-9]'.