使用 exec 重定向所有后续命令的 stderr

使用 exec 重定向所有后续命令的 stderr

我有一个 bash 文件,需要将所有输出重定向到一个文件、调试日志以及终端。我需要将 stdout 和 stderr 重定向到调试并记录脚本中的所有命令。

我不想2>&1 | tee -a $DEBUG为文件中的每个命令添加。我可以和 一起生活| tee -a $DEBUG

我记得有一种方法可以用类似的东西来做到这一点exec 2>&1

目前我正在使用类似以下的东西:

#!/bin/bash
DEBUGLOG=/tmp/debug
exec 2>&1
somecommand | tee -a $DEBUGLOG
somecommand2 | tee -a $DEBUGLOG
somecommand3 | tee -a $DEBUGLOG

但它不起作用。有谁有解决方案/可以解释原因吗?

答案1

您可以在脚本顶部像这样使用 exec:

exec > >(tee "$HOME/somefile.log") 2>&1

例如:

#!/bin/bash -

exec > >(tee "$HOME/somefile.log") 2>&1

echo "$HOME"
echo hi
date
date +%F
echo bye 1>&2

让我输出到文件$HOME/somefile.log和终端,如下所示:

/home/saml
hi
Sun Jan 20 13:54:17 EST 2013
2013-01-20
bye

答案2

至于一次重定向大量命令的解决方案:

#!/bin/bash
{
    somecommand 
    somecommand2
    somecommand3
} 2>&1 | tee -a $DEBUGLOG

为什么您的原始解决方案不起作用: exec 2>&1 会将标准错误输出重定向到 shell 的标准输出,如果您从控制台运行脚本,则该标准输出将是您的控制台。命令上的管道重定向将仅重定向命令的标准输出。

从 的角度来看somecommand,它的标准输出进入连接到的管道tee,标准错误进入与 shell 的标准错误相同的文件/伪文件,您将其重定向到 shell 的标准输出,这将是console 如果您从控制台运行程序。

解释它的一种真正方法是看看到底发生了什么:

如果您从终端运行 shell,其原始环境可能如下所示:

stdin -> /dev/pts/42
stdout -> /dev/pts/42
stderr -> /dev/pts/42

将标准错误重定向到标准输出 ( exec 2>&1) 后,您......基本上没有任何改变。但是,如果将脚本的标准输出重定向到文件,最终会得到如下环境:

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /dev/pts/42

然后将 shell 标准错误重定向到标准输出最终会像这样:

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /your/file

运行命令将继承此环境。如果您运行命令并将其通过管道传输到 tee,则该命令的环境将是:

stdin -> /dev/pts/42
stdout -> pipe:[4242]
stderr -> /your/file

因此,您的命令的标准错误仍然会进入 shell 用作其标准错误的内容。

实际上,您可以通过查看/proc/[pid]/fd: use来查看命令的环境ls -l,还可以列出符号链接的内容。这里的文件0是标准输入,1是标准输出,2是标准错误。如果该命令打开更多文件(大多数程序都会这样做),您也会看到它们。程序还可以选择重定向或关闭其标准输入/输出并重用0,12

答案3

将 stderr 和 stdout 写入文件,在屏幕上显示 stderr(在 stdout 上)

exec 2> >(tee -a -i "$HOME/somefile.log")
exec >> "$HOME/somefile.log"

对于 cron 很有用,因此您可以通过邮件接收错误(并且仅接收错误)

答案4

手册script页有一些警告:

不建议在非交互式 shell 中运行脚本。脚本的内壳始终是交互式的,这可能会导致意外的结果。

您还应该避免在命令管道中使用脚本,因为脚本可以读取比您预期更多的输入。

script我认为当标准输入和/或标准输出将被重定向时使用它是不安全的。

运行命令时我会这样做:

output=$("<command> <args>" 2> tee -a $LOGFILE >&2)

据了解,OP 并不希望对每个命令都执行完全相同的操作,但您可以轻松定义一个函数

run () { ... output=$("$(@) 2> tee -a $LOGFILE >&2"; ... }

这使您能够捕获常规输出并对其进行处理,同时记录警告和错误,并同时将它们显示在 stderr 上。

然后你可以做

[ $? -eq 0 ] && echo "<command> <args> succeeded! Output:\n$output\n(EOF)" >>$LOGFILE

如果需要的话还可以记录常规输出。

相关内容