将stdout的COPYredirect到bash脚本本身的日志文件

我知道如何将stdoutredirect到一个文件:

exec > foo.log echo test 

这将把“testing”放到foo.log文件中。

现在我想将输出redirect到日志文件并保持在标准输出

也就是说,可以从脚本之外轻松完成:

 script | tee foo.log 

但我想从内部做到这一点

我试过了

 exec | tee foo.log 

但它没有工作

 #!/usr/bin/env bash # Redirect stdout ( > ) into a named pipe ( >() ) running "tee" exec > >(tee -i logfile.txt) # Without this, only stdout would be captured - ie your # log file would not contain any error messages. # SEE (and upvote) the answer by Adam Spiers, which keeps STDERR # as a separate stream - I did not want to steal from him by simply # adding his answer to mine. exec 2>&1 echo "foo" echo "bar" >&2 

请注意,这是bash ,而不是sh 。 如果使用sh myscript.sh调用脚本,则会syntax error near unexpected token '>'syntax error near unexpected token '>'行中syntax error near unexpected token '>'

如果您正在使用信号陷阱,则可能需要使用tee -i选项,以避免发生信号时中断输出。 (感谢JamesThomasMoon1979的评论。)


根据是否写入pipe道或terminal(例如,使用颜色和列化输出)来更改输出的工具将检测到上述构造,意​​思是它们输出到pipe道。

有选项来强制着色/列化(例如ls -C --color=always )。 请注意,这将导致颜色代码被写入日志文件,从而使其不易读。

接受的答案不会将STDERR保存为单独的文件描述符。 这意味着

 ./script.sh >/dev/null 

将不会输出bar到terminal,只能输出到日志文件中

 ./script.sh 2>/dev/null 

将输出foobar到terminal。 显然,这不是普通用户可能期望的行为。 这可以通过使用两个附加到同一个日志文件的单独的tee进程来解决:

 #!/bin/bash # See (and upvote) the comment by JamesThomasMoon1979 # explaining the use of the -i option to tee. exec > >(tee -ia foo.log) exec 2> >(tee -ia foo.log >&2) echo "foo" echo "bar" >&2 

(请注意,上述内容最初并不会截断日志文件 – 如果您想要添加这种行为

 >foo.log 

到脚本的顶部。)

tee(1)的POSIX.1-2008规范要求输出是无缓冲的,即不是行缓冲的,所以在这种情况下,STDOUT和STDERR可能会在foo.log的同一行上结束; 然而这也可能发生在terminal上,所以日志文件将是对terminal上可以看到的东西的忠实反映,如果不是精确的镜像。 如果希望STDOUT行与STDERR行干净地分离,可以考虑使用两个日志文件,每行可能带有date标记前缀,以便稍后按时间顺序重新组合。

busybox和非bash shell的解决scheme

接受的答案当然是bash的最佳select。 我在Busybox环境中工作而不访问bash,而且它不理解exec > >(tee log.txt)语法。 它也不会正确执行exec >$PIPE ,试图创build一个与命名pipe道同名的普通文件,该文件会失败并挂起。

希望这对没有bash的其他人有用。

而且,对于任何使用命名pipe道的人来说, rm $PIPE都是安全的,因为这样可以将pipe道从VFS中断开,但是使用它的进程仍然保持引用计数,直到完成。

注意使用$ *不一定安全。

 #!/bin/sh if [ "$SELF_LOGGING" != "1" ] then # The parent process will enter this branch and set up logging # Create a named piped for logging the child's output PIPE=tmp.fifo mkfifo $PIPE # Launch the child process without redirected to the named pipe SELF_LOGGING=1 sh $0 $* >$PIPE & # Save PID of child process PID=$! # Launch tee in a separate process tee logfile <$PIPE & # Unlink $PIPE because the parent process no longer needs it rm $PIPE # Wait for child process running the rest of this script wait $PID # Return the error code from the child process exit $? fi # The rest of the script goes here 

在你的脚本文件里面,把所有的命令放在括号内,如下所示:

 ( echo start ls -l echo end ) | tee foo.log 

简单的方法来做一个bash脚本日志到syslog。 脚本输出可通过/var/log/syslog和stderr获得。 系统日志将添加有用的元数据,包括时间戳。

在顶部添加这一行:

 exec &> >(logger -t myscript -s) 

或者,将日志发送到一个单独的文件:

 exec &> >(ts |tee -a /tmp/myscript.output >&2 ) 

这需要moreutils (用于添加时间戳的ts命令)。

使用接受的答案,我的脚本保持exception提前(在'exec>>(tee …)'之后),让我的脚本的其余部分在后台运行。 由于我无法按照自己的方式获得解决scheme,因此我发现了另一个解决scheme/解决此问题的方法:

 # Logging setup logfile=mylogfile mkfifo ${logfile}.pipe tee < ${logfile}.pipe $logfile & exec &> ${logfile}.pipe rm ${logfile}.pipe # Rest of my script 

这使脚本的输出从进程,通过pipe道进入'tee'的子后台进程,将所有内容logging到光盘和脚本的原始标准输出。

请注意,'exec&>'redirectstdout和stderr,如果我们喜欢,我们可以单独redirect它们,或者如果我们只需要stdout,可以更改为'exec'。

即使在脚本开始时将pipe道从文件系统中移除,它仍将继续运行,直到进程结束。 我们不能使用rm-line之后的文件名来引用它。

Bash 4有一个coproc命令,它为命令build立一个命名pipe道,并允许你通过它进行通信。

这些都不是一个完美的解决scheme,但这里有一些你可以尝试的东西:

 exec >foo.log tail -f foo.log & # rest of your script 

要么

 PIPE=tmp.fifo mkfifo $PIPE exec >$PIPE tee foo.log <$PIPE & # rest of your script rm $PIPE 

第二个会留下一个pipe道文件,如果你的脚本出了问题,这可能是也可能不是问题(也许你可以在之后的父shell)。