假设您在 bash 中将命令的标准输出重定向cmd
到名为 的文件f.out
,并将标准错误重定向到f.err
,使用tee
保留控制台打印:
cmd 1> >(tee f.out) 2> >(tee f.err)
然后f.out
包含输出以及错误(至少在我的系统上)。
现在,如果您更改重定向的顺序:
cmd 2> >(tee f.err) 1> >(tee f.out)
f.out
仅包含输出(并且f.err
仅包含两种情况下的错误)。
所以我的问题是双重的:如何将 stderr 重定向到f.out
,以及为什么重定向的顺序会影响结果?
请注意,如果您不使用tee
, 但例如cat
,如下所示:
cmd 1> >(cat>f.out) 2> >(cat>f.err)
您没有这个问题,并且重定向的顺序并不重要,正如预期的那样,并且在没有进程替换的情况下(cmd 1>f.out 2>f.err
)。
答案1
重定向的顺序很重要,因为 Bash 按照在它解释的命令中找到它们的顺序应用它们。
这是故意的,这样您就可以有像> file 2>&1
按预期工作那样的习惯用法,即让 stderr 与 stdout 相同。这个惯用语的工作原理与“分配file
给 stdout,然后使 stderr 等于 stdout”一样,这会产生预期的结果,因为当 stderr 获得 stdout 的相同值时,stdout 的值是file
。另一种方式(即2>&1 1> file
)不会产生相同的结果,因为 stdout 的值在复制到 stderr 的值后发生了更改。文件描述符可以被认为类似于常规变量,它们有自己的值,并且可以用来获取另一个变量值的副本,如 中var1="${var2}"
,并且很像这样var1
不会跟随var2
的后续值更改,文件描述符的值也不会。
它也很方便,例如您可以在同一行上交换文件描述符,就像在3>&1 1>&2 2>&3-
.这使用 fd 3 作为临时“助手”fd 来交换 fd 1 和 2。
因此,您可以将重定向视为按顺序执行的指令,就像它们位于命令或脚本的两个单独行上一样。
根据您的具体情况,还涉及流程替换,并且这些替换也会按指定的顺序执行继承到目前为止表达的重定向
也就是说,总而言之:
- 首先将 stdout 重定向到正在运行的进程
tee f.out
;此时cmd
的 stdout 根据需要连接到tee f.out
的 stdin - 然后将 stderr 重定向到正在运行的进程
tee f.err
;但这按照之前表达的重定向继承了它的标准输出,即连接到tee f.out
的标准输入
因此tee f.err
,通过无害地输出到其 stdout 以及 f.err 文件,将您的cmd
错误消息通过管道传输到tee f.out
stdin,从而接收所有消息,将它们输出到 f.out 文件以及终端窗口。
答案2
我遇到了这个问题,这是我正在进行的测试的答案的一部分。该命令的一般情况是运行 cmd 并在终端上显示,修改 stderr(缩进并着色为红色),然后将屏幕上显示的内容保存在日志文件中。从日志中删除颜色编码。
因此,创建 2 个 stdout 和 2 个 stderr 行的示例 cmd 是
{ ls -l /usr/bin/col{1,2,a,b} 2>&1 >&3 | sed 's/.*/\t\o33[31mSTDERR: &\o33[0m/'; } 3>&1
现在,将副本放入日志文件中
{ ls -l /usr/bin/col{1,2,a,b} 2>&1 >&3 | sed 's/.*/\t\o33[31mSTDERR: &\o33[0m/'; } 3>&1 | tee >( sed 's/\o33//g;s/\[31m//g;s/\[0m//' > /tmp/file )
然而,这工作得很好;有更好的方法来实现这一目标吗?