【问题标题】:pipe stdout and stderr to two different processes in shell script?将 stdout 和 stderr 管道传输到 shell 脚本中的两个不同进程?
【发布时间】:2012-02-25 03:30:01
【问题描述】:

我有一个管道正在做

 command1 | command2

因此, command1 的 stdout 转到 command2 ,而 command1 的 stderr 转到终端(或 shell 的任何 stdout 所在的位置)。

当 stdout 仍将传输到 command2 时,如何将 command1 的 stderr 通过管道传输到第三个进程 (command3)?

【问题讨论】:

    标签: bash shell pipe io-redirection


    【解决方案1】:

    使用另一个文件描述符

    { 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
    

    【讨论】:

    • 如何添加文件描述符? echo out &gt;&amp;3 输出“-bash: 3: Bad file descriptor”
    • antak 下面的回答比较完整。它仍然保持 stdout 和 stderr 之间的原始分隔,就像没有所有管道的命令一样。请注意,对于管道,命令在子进程中运行。如果您不希望这样,因为您可能希望命令修改全局变量,则需要创建 fifo 并改用重定向。
    • 谢谢,@oHo。顺便说一句,有没有办法保留command 的退出代码,尤其是。鉴于tee 吃掉了它。 IE。下一条命令rc=$?0 保存到rc
    【解决方案2】:

    接受的答案导致 stdoutstderr 的反转。这是一种保存它们的方法(因为谷歌搜索会出现这篇文章):

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

    注意:

    • 需要3&gt;&amp;- 以防止fd 3command 继承。 (因为这可能会导致意外结果,具体取决于 command 在内部所做的事情。)

    部分说明:

    1. 外在先:

      1. 3&gt;&amp;1 -- { ... }fd 3 设置为 fd 1 是什么(即stdout
      2. 1&gt;&amp;2 -- { ... }fd 1 设置为 fd 2 是什么(即stderr
      3. | stdout_command -- fd 1 (原为stdout) 通过stdout_command 进行管道传输
    2. 内部从外部继承文件描述符:

      1. 2&gt;&amp;1 -- commandfd 2 设置为 fd 1 是什么(即stderr 根据外部)
      2. 1&gt;&amp;3 -- commandfd 1 设置为 fd 3 是什么(即stdout 根据外部)
      3. 3&gt;&amp;- -- commandfd 3 设置为空(即关闭
      4. | stderr_command -- fd 1 (原为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 -&gt; cb -&gt; d 的顺序总是不确定的,因为 stderr_commandstdout_command 之间没有同步形式。)

    【讨论】:

    • 这个东西有效,我验证了它,但我无法理解它是如何工作的。在外部,第 3 点 stdout_command 不是 fd1 现在指向 stderr,stdout 是如何去那里而不是 stderr 的。
    • 事实上这也有效 (command 2>&1 | stderr_command; ) 1>&2 |标准输出命令
    • @RahulKadukar 这将command 中的stdoutstderr 都放在stderr_command 之间,而stdout_command 什么也没有。
    • 我很高兴解开这个问题,谢谢(:注意:您可以通过将最里面的重定向设置为仅2&gt;&amp;3 3&gt;&amp;- 来缩短它。但是,这意味着您需要在在 curlies 的内部和外部的 stderr(因此,在您的示例中交换 stdin_commandstdout_command)。
    • @jwd 感谢您的评论。 :) 这种方法的问题是整个命令行的stdoutstderr 出现反转。我通过在命令行末尾添加&gt;/dev/null 来测试它,看看是否只有ac 被过滤掉了。
    【解决方案3】:

    使用进程替换:

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

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

    【讨论】:

    • 注意:这不是 POSIX 而是 bashism。
    • 它也比 POSIX 解决方案好得多。
    【解决方案4】:

    只需将标准错误重定向到标准输出

    { 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
    

    【讨论】:

    • 哎呀,好电话。我最初认为 OP 希望 stderr only 转到command 3。这看起来是正确的方法。
    • 不会{ command1 | command2 &gt;/dev/null 2&gt;&amp;1 } 2&gt;&amp;1 | command3 阻止 command2 的 stdout/stderr 到达 command3 ,或者这也会与 command1 的 stderr 混淆?
    • 嗨@user964970。 /dev/null 重定向是个好主意。正如您所说,您上面的示例混淆了stderrstdout,因为它们在同一步骤中被反转。我更喜欢{ command1 | command2 &gt;/dev/null; } 2&gt;&amp;1 | command3。我编辑我的答案以使用您的出色贡献。谢谢;-)
    • 这个答案的一个问题是 { } 创建了一个子shell,在某些情况下这是不可接受的。例如,您不能将变量传回 {}。
    【解决方案5】:

    像往常一样管道标准输出,但使用 Bash 进程替换来进行标准错误重定向:

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

    标头:#!/bin/bash

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 这对我来说在 zsh 中有效,除了 stderr 和 stdout 命令为我翻转。 bash 和 zsh。在我看来,您正在将标准输出重定向到 3,然后将 3 路由回标准输出,所以最后一条语句应该是标准输出。我不知道。
      【解决方案7】:

      使用 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&gt;err。可以看到,每个wc -c 都有 20 个字符的输入。

      如果您不想让它闲置,请不要忘记在完成后清理 fifo(即rm)。如果另一个命令想要在 stdin 上输入而不是文件 arg,您也可以使用像 wc -c &lt; err 这样的输入重定向。

      【讨论】:

      • 看起来 OP 希望 both stdoutstderr 转到 command2,我最初错过了。以上将两者分开并分别发送到命令。不过我会留下它,因为它可能对某人有用。
      • 不,我不希望 stdout 和 stderr 都进入 command2。 command1 到 command2 的标准输出,command1 到 command3 的标准错误。 command2 不应该得到 command1 的标准错误
      猜你喜欢
      • 2015-10-12
      • 2018-10-03
      • 2015-06-21
      • 2019-03-21
      • 1970-01-01
      • 2012-02-26
      • 1970-01-01
      • 2020-09-26
      • 1970-01-01
      相关资源
      最近更新 更多