将 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.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” 在这里找到答案:unix.stackexchange.com/questions/18899/… 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 在内部所做的事情。)

部分说明:

    外在先:

      3&gt;&amp;1 -- ... fd 3 设置为 fd 1 是什么(即stdout1&gt;&amp;2 -- ... fd 1 设置为 fd 2 是什么(即stderr| stdout_command -- fd 1 (原为stdout) 通过stdout_command 进行管道传输

    内部从外部继承文件描述符:

      2&gt;&amp;1 -- commandfd 2 设置为 fd 1 是什么(即stderr 根据外部) 1&gt;&amp;3 -- commandfd 1 设置为 fd 3 是什么(即stdout 根据外部) 3&gt;&amp;- -- commandfd 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 -&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 的标准错误

以上是关于将 stdout 和 stderr 管道传输到 shell 脚本中的两个不同进程?的主要内容,如果未能解决你的问题,请参考以下文章

我如何在 .NET 中管道 stdout/stderr?

管道输出subprocess.Popen到文件

命令输出(stdout,stderr)未重定向到管道

如何在 Windows 中访问继承的匿名管道句柄,而不是 stdout、stderr 和 stdin?

使用 stdout/stderr 以外的管道与子进程通信

在自动 ftp 脚本中通过管道将 stderr 传输到 syslog