我有一个 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
,1
和2
。
答案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
如果需要的话还可以记录常规输出。