将标准输出的副本从bash脚本本身重定向到日志文件

将标准输出的副本从bash脚本本身重定向到日志文件,bash,shell,redirect,logging,Bash,Shell,Redirect,Logging,我知道如何将标准输出重定向到文件: exec > foo.log echo test 这将把“测试”放入foo.log文件中 现在我想将输出重定向到日志文件中,并将其保留在标准输出上 i、 e.可以从脚本外部轻松完成: script | tee foo.log 但是我想在脚本本身中声明它 我试过了 exec | tee foo.log 但它不起作用。这两种方法都不是完美的解决方案,但以下是一些您可以尝试的方法: exec >foo.log tail -f foo.log &am

我知道如何将标准输出重定向到文件:

exec > foo.log
echo test
这将把“测试”放入foo.log文件中

现在我想将输出重定向到日志文件中,并将其保留在标准输出上

i、 e.可以从脚本外部轻松完成:

script | tee foo.log
但是我想在脚本本身中声明它

我试过了

exec | tee foo.log

但它不起作用。

这两种方法都不是完美的解决方案,但以下是一些您可以尝试的方法:

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

PIPE=tmp.fifo
mkfifo$管道
exec>$PIPE

tee foo.log在脚本文件中,将所有命令放在括号内,如下所示:

(
echo start
ls -l
echo end
) | tee foo.log
bash4有一个命令,它建立一个到命令的命名管道,并允许您通过它进行通信

#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. 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
调用脚本,您将在意外标记“>”附近的
语法错误行中得到一个错误

如果您使用的是信号陷阱,您可能希望使用
tee-i
选项,以避免在出现信号时中断输出。(感谢Jamesthomasmon1979的评论。)


根据是否写入管道或终端来更改输出的工具(例如,使用颜色和列化输出的ls
)将检测上述构造,即它们输出到管道


有一些选项可以强制着色/列化(例如
ls-C--color=always
)。请注意,这将导致颜色代码也写入日志文件,使其可读性降低。

busybox、macOS bash和非bash Shell的解决方案

公认的答案无疑是bash的最佳选择。我在没有访问bash的Busybox环境中工作,它不理解
exec>>(teelog.txt)
语法。它还无法正确执行
exec>$PIPE
,试图创建一个与命名管道同名的普通文件,该文件将失败并挂起

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

另外,对于使用命名管道的任何人来说,使用
rm$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 with stdout 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, which is 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
#/垃圾箱/垃圾箱
如果[“$SELF_LOGGING”!=“1”]
然后
#父进程将进入此分支并设置日志记录
#创建命名管道以记录子级的输出
管道=tmp.fifo
mkfifo$管道
#启动子进程,将stdout重定向到指定的管道
自记录=1 sh$0$*>$PIPE&
#保存子进程的PID
PID=$!
#在单独的过程中启动tee

tee日志文件使用接受的答案,我的脚本一直异常早地返回(就在'exec>>(tee…)之后),剩下的脚本在后台运行。由于我无法按自己的方式解决该问题,我找到了另一个解决方案:

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

# Rest of my script
#日志记录设置
logfile=mylogfile
mkfifo${logfile}.pipe
tee<${logfile}.pipe$logfile&
exec&>${logfile}.pipe
rm${logfile}.pipe
#我脚本的其余部分
这使得脚本的输出从进程通过管道进入“tee”的子后台进程,该进程将所有内容记录到光盘和脚本的原始stdout

请注意,“exec&>”重定向stdout和stderr,如果愿意,我们可以分别重定向它们,如果只需要stdout,也可以更改为“exec>”


即使在脚本开始时将管道从文件系统中删除,它仍将继续运行,直到进程完成。我们不能使用rm行后面的文件名引用它。

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

./script.sh >/dev/null
不会将
输出到终端,仅输出到日志文件,以及

./script.sh 2>/dev/null
foo
bar
输出到终端。显然不是这样 正常用户可能期望的行为。这可能是 通过使用两个单独的T形三通进程进行修复,这两个进程都附加到同一个进程 日志文件:

#!/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
到脚本的顶部。)


要求输出是无缓冲的,即甚至没有行缓冲,因此在这种情况下,STDOUT和STDERR可能会在
foo.log
的同一行结束;然而,这也可能发生在终端上,因此日志文件将忠实地反映终端上可以看到的内容,如果不是它的精确镜像的话。如果您想将STDUT行与STDRR线分开,请考虑使用两个日志文件,可能在每行上加上日期戳前缀以允许稍后的时间重新组装。

< P>轻松将BASH脚本日志转换为yslog。脚本输出可通过
/var/log/syslog
和stderr获得。syslog将添加有用的元数据,包括时间戳

在顶部添加此行:

exec &> >(logger -t myscript -s)
或者,将日志发送到单独的文件:

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

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

不能说我对任何基于exec的解决方案都感到满意。我更喜欢直接使用tee,因此我在请求时使用tee调用脚本本身:

# my script: 

check_tee_output()
{
    # copy (append) stdout and stderr to log file if TEE is unset or true
    if [[ -z $TEE || "$TEE" == true ]]; then 
        echo '-------------------------------------------' >> log.txt
        echo '***' $(date) $0 $@ >> log.txt
        TEE=false $0 $@ 2>&1 | tee --append log.txt
        exit $?
    fi 
}

check_tee_output $@

rest of my script
这允许您执行以下操作:

your_script.sh args           # tee 
TEE=true your_script.sh args  # tee 
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args           # tee

您可以自定义此选项,例如,将tee=false改为默认值,将tee改为保存日志文件,等等。我想此解决方案类似于jbarlow的解决方案,但更简单,也许我的解决方案有一些我还没有遇到的限制

tail将在第二个脚本中留下一个正在运行的进程tee将被阻止,或者您需要使用&t运行它,在这种情况下,它将使进程与第一个脚本中的进程保持一致。@Vitaly:oops,忘记后台
teeyour_script.sh args           # tee 
TEE=true your_script.sh args  # tee 
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args           # tee