将stdout和stderr传递到Shell脚本中的两个不同进程?

问题描述 投票:54回答:7

我有一条管道正在做

 command1 | command2

因此,command1的stdout进入command2,而command1的stderr进入终端(或shell的stdout所在的地方)。

如何在stdout仍要传递给command2的同时,将command1的stderr传递给第三进程(command3

bash shell pipe io-redirection
7个回答
53
投票

使用另一个文件描述符

{ command1 2>&3 | command2; } 3>&1 1>&2 | command3

您最多可以使用7个其他文件描述符:3到9。如果您需要更多解释,请询问,我可以解释;-)

测试

{ { echo a; echo >&2 b; } 2>&3 | sed >&2 's/$/1/'; } 3>&1 1>&2 | sed 's/$/2/'

输出:

b2
a1

示例

产生两个日志文件:1.仅stderr2. stderrstdout

{ { { command 2>&1 1>&3; } | tee err-only.log; } 3>&1; } > err-and-stdout.log

如果commandecho "stdout"; echo "stderr" >&2,我们可以像这样测试它:

$ { { { echo out>&3;echo err>&1;}| tee err-only.log;} 3>&1;} > err-and-stdout.log
$ head err-only.log err-and-stdout.log
==> err-only.log <==
err

==> err-and-stdout.log <==
out
err

30
投票

接受的答案导致stdoutstderr取反。这是一种保留它们的方法(因为出于这个目的而使用谷歌搜索功能会出现此帖子):

{ command 2>&1 1>&3 3>&- | stderr_command; } 3>&1 1>&2 | stdout_command

通知:

  • 3>&-是必需的,以防止fd 3command继承。 (因为这可能会导致意外结果,具体取决于command内部执行的操作。)

解释的部分:

  1. 外部优先:

      3>&1
    1. { ... }-fd 3设置为fd 1的值(即stdout
    2. 1>&2
    3. { ... }-fd 1设置为fd 2的值(即stderr
    4. | stdout_command-fd 1(是stdout)通过stdout_command]传递>
  2. 内部部分从外部部分继承文件描述符:

    1. 将[2>&1
    2. command-fd 2设置为fd 1的值(即,外部的stderr)]将[1>&3
    3. command-fd 1
    4. 设置为fd 3的值(即,外部的stdout)]
    5. 3>&--fd 3
    6. 对于command设置为无(即closed
    7. | stderr_command-fd 1
    8. (是stderr)通过stderr_command]传递>

    示例:
    foo() {
        echo a
        echo b >&2
        echo c
        echo d >&2
    }
    
    { foo 2>&1 1>&3 3>&- | sed -u 's/^/err: /'; } 3>&1 1>&2 | sed -u 's/^/out: /'
    

输出:
out: a
err: b
err: d
out: c

((a -> cb -> d的顺序始终是不确定的,因为stderr_commandstdout_command之间没有任何形式的同步。)

仅将stderr重定向到stdout

{ command1 | command2; } 2>&1 | command3

注意: commnd3也将读取command2标准输出(如果有)。为了避免这种情况,您可以舍弃commnd2 stdout:

{ command1 | command2 >/dev/null; } 2>&1 | command3

但是,要保持command2标准输出(例如在终端中,然后请参考我的其他更复杂的答案。

测试
{ { echo -e "a\nb\nc" >&2; echo "----"; } | sed 's/$/1/'; } 2>&1 | sed 's/$/2/'

输出:

a2
b2
c2
----12

使用进程替换:

command1 > >(command2) 2> >(command3)

请参阅http://tldp.org/LDP/abs/html/process-sub.html了解更多信息。

使用fifo可以很容易地实现相同的效果。我不知道执行此操作的直接管道语法(尽管看到一个语法很不错)。这就是您可以使用fifo进行操作的方式。

首先,打印到stdoutstderrouterr.sh的东西:

#!/bin/bash

echo "This goes to stdout"
echo "This goes to stderr" >&2

然后我们可以做这样的事情:

$ mkfifo err
$ wc -c err &
[1] 2546
$ ./outerr.sh 2>err | wc -c
20
20 err
[1]+  Done                    wc -c err

以这种方式,您首先为stderr输出设置了侦听器,并使用语法2>err对其进行阻塞,直到具有写程序为止,该操作在下一个命令中发生。您可以看到每个wc -c都有20个字符的输入。

[如果您不希望它挂在身边(rm),请不要忘记在完成后清除它。如果其他命令希望在stdin上输入而不是文件arg,则也可以使用wc -c < err这样的输入重定向。

与往常一样使用管道标准输出,但是使用Bash进程替代标准错误重定向:

some_command 2> >(command of stderr) | command of stdout

标题:#!/bin/bash

Zsh版本

我喜欢@antak发布的answer,但由于multios,它在zsh中无法正常工作。这是在zsh中使用它的一个小调整:

{ unsetopt multios; command 2>&1 1>&3 3>&- | stderr_command; } 3>&1 1>&2 | stdout_command

要使用,将command替换为您要运行的命令,然后将stderr_commandstdout_command替换为所需的管道。例如,命令ls / /foo将同时生成stdout输出和stderr输出,因此我们可以将其用作测试用例。要将stdout保存到名为stdout的文件中,并将stderr保存到名为stderr的文件中,可以执行以下操作:

{ unsetopt multios; ls / /foo 2>&1 1>&3 3>&- | cat >stderr; } 3>&1 1>&2 | cat >stdout

请参阅@antak的原始答案以获取完整说明。


15
投票

仅将stderr重定向到stdout


10
投票

使用进程替换:


1
投票

使用fifo可以很容易地实现相同的效果。我不知道执行此操作的直接管道语法(尽管看到一个语法很不错)。这就是您可以使用fifo进行操作的方式。


0
投票

与往常一样使用管道标准输出,但是使用Bash进程替代标准错误重定向:


0
投票

Zsh版本

© www.soinside.com 2019 - 2024. All rights reserved.