将标准输出和标准错误分别重定向到不同的文件,同时保留它们在各自流中的内容。

16

我试图编写一个脚本,基本上充当一个由非交互式命令创建的所有输出的传递日志,而不影响命令对其他进程的输出。也就是说,标准输出和标准错误应该与没有经过我的命令运行时一样。

为了做到这一点,我尝试将标准输出和标准错误分别重定向到两个不同的Tee中,每个Tee都用于不同的文件,然后重新组合它们,以便它们仍然出现在标准输出和标准错误上。我看到很多关于Tee和重定向的其他问题,并尝试了一些从中获得的答案,但没有一个似乎能够正确地将流分离到单独的Tee中并重新组合它们。

我的尝试成功地将输出拆分到正确的文件中,但是流不能正确地保留实际的标准输出/标准错误输出。我在更复杂的设置中看到了这一点,所以我创建了简化的命令,其中我将数据回显到标准输出或标准错误作为我的“命令”,如下所示。

这里有一些我尝试过的事情:

{ command | tee ~/tee.txt; } 2>&1 | { tee ~/tee2.txt 1>&2; }

运行我的简单测试,我看到:

$ { { { echo "test" 1>&2; } | tee ~/tee.txt; } 2>&1 | { tee ~/tee2.txt 1>&2; } } > /dev/null
test
$ { { { echo "test" 1>&2; } | tee ~/tee.txt; } 2>&1 | { tee ~/tee2.txt 1>&2; } } 2> /dev/null
$

好的,这正是我所期望的。我将输出到stderr,因此当我将最终的stderr重定向到/dev/null时,我希望看不到任何内容,而当我仅重定向stdout时,我希望看到我的原始输出。

$ { { { echo "test";  } | tee ~/tee.txt; } 2>&1 | { tee ~/tee2.txt 1>&2; } } > /dev/null
test
$ { { { echo "test";  } | tee ~/tee.txt; } 2>&1 | { tee ~/tee2.txt 1>&2; } } 2> /dev/null
$

这是反过来的!我的命令只将数据发送到stdout,因此当我将最终的stdout重定向到null时,我希望看不到任何东西。但相反的情况发生了。

这是我尝试的第二个命令,它有点更复杂:

{ command 2>&3 | tee ~/tee.txt; } 3>&1 1>&2 | { tee /home/michael/tee2.txt 1>&2; }

很遗憾,我看到了与以前完全相同的行为。

我不太清楚自己做错了什么,但似乎stdout出现了某些问题。对于第一条命令,我怀疑是因为在把stdout和stderr (2>&1) 组合后再将其流到第二个tee时发生了冲突,但如果是这种情况,我希望在tee2.txt文件中看到stdout和stderr两个输出,但实际上只有stderr!对于第二条命令,我根据我所读到的答案得出的印象是,为避免这个问题,描述符正在被交换,但显然还是出了问题。

编辑:我又想到了一个可能性,即第二个命令失败是因为我正在重定向1>&2,这会杀死第一个tee的stdout。所以我尝试使用1>&4进行重定向,然后在最后将其重定向回stdout:

{ command 2>&3 | tee ~/tee.txt; } 3>&1 1>&4 | { tee /home/michael/tee2.txt 1>&2 4>&1; }

但现在我明白了:

-bash: 4: Bad file descriptor

最后我也尝试将描述符2重定向回到描述符1中:

{ command 2>&3 | tee ~/tee.txt; } 3>&1 1>&2 | { tee /home/michael/tee2.txt 1>&2 2>&1; }

和:

{ command 2>&3 | tee ~/tee.txt; } 3>&1 1>&2 | { tee /home/michael/tee2.txt 1>&2; } 2>&1
1个回答

15

基于进程替换的解决方案是简单的,尽管可能不像您想象的那么简单。我的第一次尝试似乎应该可以工作

{ echo stdout; echo stderr >&2; } > >( tee ~/stdout.txt ) \
                                 2> >( tee ~/stderr.txt )
然而,在bash中,它并不能像预期那样工作,因为第二个tee继承了原始命令的标准输出(因此它会被发送到第一个tee),而不是来自调用shell。目前尚不清楚这是否应该被视为bash的一个bug。
可以通过将输出重定向分成两个单独的命令来解决这个问题。
{ { echo stdout; echo stderr >&2; } > >(tee stdout.txt ); } \
                                   2> >(tee stderr.txt )

更新:第二个 tee 应该实际上是 tee stderr.txt >&2 这样从标准错误输出的内容才会被打印回标准错误输出。

现在,标准错误输出的重定向发生在一个没有其标准输出被重定向的命令中,因此它按照预期方式工作。外部复合命令将其标准错误输出重定向到外部的 tee,而其标准输出保留在终端上。 内部复合命令从外部继承其标准错误输出(因此它也传输到了外部的tee),而其标准输出则被重定向到内部的 tee


这似乎没有保持原始的 stderrstdout 分开,而是将它们合并了。例如,如果我在整个语句中包装 { ... } > /dev/null,我看不到任何输出,但如果我将其包装在 { ... } 2> /dev/null 中,我会看到 "stdout" 和 "stderr"。 - Michael
1
将第二个 tee 改为 (tee stderr.txt 1&2) 看起来达到了预期的效果。 - Michael
假设你的意思是 2> >(tee stderr 1>&2),是的,我忘记将 tee 的输出恢复到标准错误了。 - chepner
6
简化版是否也适用于这个更正?我认为最好在代码块中更新答案并在注释中进行解释,而不是让错误版本占据主导地位。 - Sam Brightman
@SamBrightman 没错,加上更正后第一个答案也是可行的。让第一个答案起作用的另一种方法是颠倒重定向命令的顺序,因为 Bash 似乎会先评估右侧的命令。 - Jean Paul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接