我有一个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. stderr
和stdout
{ { { 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
接受的答案会导致stdout
和stderr
的反转。 这里有一个保留它们的方法(因为谷歌搜索这个目的会弹出这篇文章(:
{ command 2>&1 1>&3 3>&- | stderr_command; } 3>&1 1>&2 | stdout_command
通知:
- 需要
3>&-
以防止 fd 3 被command
继承。 (因为这可能会导致意外的结果,具体取决于command
内部的作用。
零件解释:
首先是外部部分:
-
3>&1
--{ ... }
的 fd 3 设置为 fd 1 的 fd 1(即stdout
( -
1>&2
--{ ... }
的 fd 1 设置为 fd 2 的 fd 2(即stderr
( -
| stdout_command
-- FD 1 (被stdout
( 通过管道传输stdout_command
-
内部部分从外部继承文件描述符:
-
2>&1
--command
的 fd 2 设置为 fd 1 的 fd 1(即stderr
根据外部( -
1>&3
--command
的 fd 1 设置为 FD 3 的 fd3(即stdout
根据外部( -
3>&-
--command
的 fd 3 设置为无(即关闭( -
| 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 -> c
和b -> d
的顺序将始终不确定,因为stderr_command
和stdout_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_command
和stdout_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 的方式。
首先,打印到stdout
和stderr
的东西,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的答案可能会颠倒输出的顺序。
下面的解决方案可能会通过正确重定向command2
和command3
输出和错误来解决这些问题,分别按预期将stdout
和stderr
,并保持顺序。
{ { command1 2>&3 | command2; } 3>&1 1>&4 | command3; } 4>&1
当然,它也满足了OP将输出和错误从command1
分别重定向到command2
和command3
的需求。