管道stdout和stderr到shell脚本中的两个不同进程?

时间:2012-02-02 13:15:31

标签: bash shell pipe io-redirection

我只是做了一个pipline

 command1 | command2

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

如果stdout仍然要命令2,我如何将command1的stderr传递给第三个进程(command3)?

7 个答案:

答案 0 :(得分: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. stderr只有  2. 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

答案 1 :(得分:25)

接受的答案会导致stdoutstderr的逆转。这是一种保留它们的方法(因为谷歌搜索就是为了这个目的):

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

注意:

  • 3>&-需要阻止 fd 3 command继承。 (因为这会导致意外结果,具体取决于command内部的内容。)

部分解释:

  1. 首先是外部部分:

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

      {li> 2>&1 - fd 2 command设置为 fd 1 (即stderr外部)
    1. 1>&3 - command fd 1 设置为 fd 3 (即外部stdout
    2. 3>&- - command fd 3 设置为空(即已关闭
    3. | stderr_command - fd 1 stderr)通过stderr_command管道传输
  3. 实施例

    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之间没有任何形式的同步。)

答案 2 :(得分:13)

只需将stderr重定向到stdout

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

警告: commnd3还会读取command2标准输出(如果有)。
为避免这种情况,您可以放弃commnd2标准输出:

{ 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

答案 3 :(得分:5)

使用流程替换:

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

有关详细信息,请参阅http://tldp.org/LDP/abs/html/process-sub.html

答案 4 :(得分:2)

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的原始答案。

答案 5 :(得分:1)

使用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),请不要忘记在完成后清理fifo。如果另一个命令想要输入stdin而不是文件arg,则可以使用输入重定向,如wc -c < err

答案 6 :(得分:0)

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

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

标题:#!/bin/bash