将 stdout 和 stderr 管道传输到 shell 脚本中的两个不同进程?
Posted
技术标签:
【中文标题】将 stdout 和 stderr 管道传输到 shell 脚本中的两个不同进程?【英文标题】:pipe stdout and stderr to two different processes in shell script? 【发布时间】:2012-02-25 03:30:01 【问题描述】:我有一个管道正在做
command1 | command2
因此, command1 的 stdout 转到 command2 ,而 command1 的 stderr 转到终端(或 shell 的任何 stdout 所在的位置)。
当 stdout 仍将传输到 command2 时,如何将 command1 的 stderr 通过管道传输到第三个进程 (command3
)?
【问题讨论】:
【参考方案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.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
【讨论】:
如何添加文件描述符?echo out >&3
输出“-bash: 3: Bad file descriptor”
在这里找到答案:unix.stackexchange.com/questions/18899/…
antak 下面的回答比较完整。它仍然保持 stdout 和 stderr 之间的原始分隔,就像没有所有管道的命令一样。请注意,对于管道,命令在子进程中运行。如果您不希望这样,因为您可能希望命令修改全局变量,则需要创建 fifo 并改用重定向。
谢谢,@oHo。顺便说一句,有没有办法保留command
的退出代码,尤其是。鉴于tee
吃掉了它。 IE。下一条命令rc=$?
将0
保存到rc
。【参考方案2】:
接受的答案导致 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 是什么(即stdout
)
1>&2
-- ...
的 fd 1 设置为 fd 2 是什么(即stderr
)
| stdout_command
-- fd 1 (原为stdout
) 通过stdout_command
进行管道传输
内部从外部继承文件描述符:
2>&1
-- command
的 fd 2 设置为 fd 1 是什么(即stderr
根据外部)
1>&3
-- command
的 fd 1 设置为 fd 3 是什么(即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
之间没有同步形式。)
【讨论】:
这个东西有效,我验证了它,但我无法理解它是如何工作的。在外部,第 3 点 stdout_command 不是 fd1 现在指向 stderr,stdout 是如何去那里而不是 stderr 的。 事实上这也有效 (command 2>&1 | stderr_command; ) 1>&2 |标准输出命令 @RahulKadukar 这将command
中的stdout
和stderr
都放在stderr_command
之间,而stdout_command
什么也没有。
我很高兴解开这个问题,谢谢(:注意:您可以通过将最里面的重定向设置为仅2>&3 3>&-
来缩短它。但是,这意味着您需要在在 curlies 的内部和外部的 stderr(因此,在您的示例中交换 stdin_command
和 stdout_command
)。
@jwd 感谢您的评论。 :) 这种方法的问题是整个命令行的stdout
和stderr
出现反转。我通过在命令行末尾添加>/dev/null
来测试它,看看是否只有a
和c
被过滤掉了。【参考方案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 >/dev/null 2>&1 2>&1 | command3
阻止 command2 的 stdout/stderr 到达 command3 ,或者这也会与 command1 的 stderr 混淆?
嗨@user964970。 /dev/null
重定向是个好主意。正如您所说,您上面的示例混淆了stderr
和stdout
,因为它们在同一步骤中被反转。我更喜欢 command1 | command2 >/dev/null; 2>&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_command
和stdout_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 的方式。
首先,打印到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(即rm
)。如果另一个命令想要在 stdin
上输入而不是文件 arg,您也可以使用像 wc -c < err
这样的输入重定向。
【讨论】:
看起来 OP 希望 bothstdout
和 stderr
转到 command2
,我最初错过了。以上将两者分开并分别发送到命令。不过我会留下它,因为它可能对某人有用。
不,我不希望 stdout 和 stderr 都进入 command2。 command1 到 command2 的标准输出,command1 到 command3 的标准错误。 command2 不应该得到 command1 的标准错误以上是关于将 stdout 和 stderr 管道传输到 shell 脚本中的两个不同进程?的主要内容,如果未能解决你的问题,请参考以下文章