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



我有一个pipline在做

 command1 | command2

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

如何将命令 1 的 stderr 管道到第三个进程 ( command3 (,而 stdout 仍然要到 command2 ?

使用其他文件描述符

{ 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

如果command echo "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

接受的答案会导致stdoutstderr的反转。 这里有一个保留它们的方法(因为谷歌搜索这个目的会弹出这篇文章(:

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

通知:

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

零件解释:

  1. 首先是外部部分:

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

    1. 2>&1 -- command的 fd 2 设置为 fd 1 的 fd 1(即 stderr根据外部(
    2. 1>&3 -- command的 fd 1 设置为 FD 3 的 fd3(即 stdout根据外部(
    3. 3>&- -- 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 -> cb -> d的顺序将始终不确定,因为stderr_commandstdout_command之间没有任何形式的同步。

使用进程替换:

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

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

只需将 stderr 重定向到 stdout

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

注意:commnd3也会读command2标准输出(如果有的话(。
为避免这种情况,您可以丢弃commnd2标准输出:

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

但是,为了保持command2标准输出(例如在终端中(,
那么请参考我另一个更复杂的答案。

测试

{ { echo -e "anbnc" >&2; echo "----"; } | sed 's/$/1/'; } 2>&1 | sed 's/$/2/'

输出:

a2
b2
c2
----12

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

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

页眉:#!/bin/bash

Zsh 版本

我喜欢 @antak 发布的答案,但由于 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 的文件,将标准输出保存到名为 stderr 的文件,您可以执行以下操作:

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

有关完整解释,请参阅@antak的原始答案。

使用FIFO可以相当容易地实现相同的效果。我不知道有直接的管道语法来做到这一点(尽管看到一个会很好(。这就是您使用 fifo 的方式。

首先,打印到stdoutstderr的东西,outerr.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 个字符的输入。

如果您不希望FIFO闲逛,请不要忘记在完成后清理FIFO(即 rm (。如果另一个命令需要输入stdin而不是文件 arg,您也可以像wc -c < err一样使用输入重定向。

已经很久了,但是...

@oHo的答案有一个缺点,即将command2输出重定向到stderr。虽然@antak的答案可能会颠倒输出的顺序。

下面的解决方案可能会通过正确重定向command2command3输出和错误来解决这些问题,分别按预期将stdoutstderr ,并保持顺序。

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

当然,它也满足了OP将输出和错误从command1分别重定向到command2command3的需求。

最新更新