将输出写入日志文件和控制台


98

在Unix Shell中,我有一个env文件(env文件定义了运行用户脚本所需的参数,例如日志文件名和路径,将输出和错误重定向到日志文件,数据库连接详细信息等),该文件将所有输出重定向(回显消息)),并使用以下代码从执行的脚本向日志文件中显示错误:

exec 1>>${LOG_FILE}
exec 2>>${LOG_FILE}

env文件在每个脚本的开头执行。由于上述env文件中的代码,所有可能是用户输出或错误的控制台输出都将直接输出到日志文件,这正是我实际需要的。

但是我希望在控制台和日志文件中同时显示一些选择性的用户输出。但是由于上面的代码,我无法执行此操作。

我知道,如果删除上面的代码,在这种情况下可以得到理想的结果,但是我将不得不将所有其他输出手动写入日志文件,这并非易事。

有没有一种方法可以在不删除上述代码的情况下在控制台和日志文件中获得输出?

Answers:


105
exec 3>&1 1>>${LOG_FILE} 2>&1

会将stdout和stderr输出发送到日志文件,但也会使fd 3连接到控制台,因此您可以执行

echo "Some console message" 1>&3

只向控制台写一条消息,或者

echo "Some console and log file message" | tee /dev/fd/3

同时向控制台日志文件写入消息- tee将其输出发送到其自己的fd 1(此处为LOG_FILE)和您要写入的文件(此处为fd 3,即控制台)。

例:

exec 3>&1 1>>${LOG_FILE} 2>&1

echo "This is stdout"
echo "This is stderr" 1>&2
echo "This is the console (fd 3)" 1>&3
echo "This is both the log and the console" | tee /dev/fd/3

会打印

This is the console (fd 3)
This is both the log and the console

在控制台上并放

This is stdout
This is stderr
This is both the log and the console

进入日志文件。


2
就像您建议的那样工作。但是我不明白tee / dev / fd / 3。我知道tee将消息写入日志文件和控制台,但是我不完全理解tee后使用的/ dev / fd / 3
abinash shrestha 2013年

我同意@shrestha这是对tee的不寻常使用。该/dev/fd/3是提到“当前打开的FD 3”文件名,所以tee /dev/fd/3会写一篇关于它的任何标准输入到达的fd 1和也的fd 3(/dev/fd/3文件)。Fd 1连接到日志文件,fd 3连接到控制台。
伊恩·罗伯茨

另外,您只想写入文件,而不要在控制台中尝试:echo "blah" | tee file1.txt | tee file2.txt >/dev/null 'Blah'不会放在file1.txt和file2.txt中,而不会写入控制台。
危险89年

这对我很有帮助。尽管我注意到可能不合时宜的事物,但也许有些人知道其原因。我从bash脚本执行R脚本。各种R函数的控制台输出都是彩色的(如我所定义)。当使用上述方法将输出重定向到控制台AND日志文件时,控制台输出不再着色。这可能是什么原因?
dieHellste

1
@dieHellste某些程序能够检测何时将其输出通过管道传送到另一个进程(在这种情况下tee,该进程又写入终端),而不是直接进入终端,并调整其输出以匹配。
伊恩·罗伯茨

43

是的,您想使用tee

tee-从标准输入读取并写入标准输出和文件

只需将命令传递到tee并将文件作为参数传递,就像这样:

exec 1 | tee ${LOG_FILE}
exec 2 | tee ${LOG_FILE}

这既将输出打印到STDOUT,又将相同的输出写入日志文件。请参阅man tee以获取更多信息。

请注意,这不会将stderr写入日志文件,因此,如果要合并两个流,请使用:

exec 1 2>&1 | tee ${LOG_FILE}

2
上述解决方案不起作用。我将redirect.env文件设置为:##### redirect.env ###### export LOG_FILE = log.txt exec 1 2>&1 | tee -a $ {LOG_FILE}执行1 | tee -a $ {LOG_FILE}执行2 | tee -a $ {LOG_FILE} #########和工作文件包含以下代码:##### output.sh ######!/ bin / sh。redirect.env echo“有效输出” ech“无效输出” ##############但我收到错误:#### redirect.env:第3行:exec:1:找不到redirect.env:第5行:exec:1:未找到redirect.env:第6行:exec:2:未找到####,并且在日志文件中,我也得到相同的错误。我做错什么了吗?
2013年

这很难说,因为注释中删除了新行。您能在gist之类的地方添加代码粘贴吗?
乔恩·凯恩斯

1
我已经将文件添加到链接UnixRedirect。相关文件是redirect.env和output.sh
abinash shrestha 2013年

1
该代码似乎不起作用(至少不再起作用)。您通常会得到script.sh: line 5: exec: 1: not found
tftd

39

我尝试了琼蒂的答案,但我也得到了

exec:1:找不到

错误。这是最适合我的方法(已确认也可以在zsh中使用):

#!/bin/bash
LOG_FILE=/tmp/both.log
exec > >(tee ${LOG_FILE}) 2>&1
echo "this is stdout"
chmmm 77 /makeError

之后的文件/tmp/both.log包含

this is stdout
chmmm command not found 

除非您从tee中删除-a,否则将附加/tmp/both.log。

提示:>(...)是流程的替代。它让exectee命令,好像它是一个文件。


1
这对我来说就像是一种魅力,而其他答案却屡见不鲜。
杰伊·泰勒

感谢您分享此代码段。这似乎工作得很好(与其他答案相比)!
tftd

这行得通,但是现在我的shell在执行后就不同了。
乔什·乌斯特

@JoshUsre如果您需要帮助或想要帮助其他SO用户,请说明有什么不同,并描述您的shell,版本,操作系统等
。– alfonx

1
如果您不需要区分stdout和stderr,可以将语句组合到exec > >(tee ${LOG_FILE}) 2>&1
darkdragon

5

我想在stdout和日志文件上显示日志以及时间戳。上面的答案对我都不起作用。我利用了进程替换exec命令,并提出了以下代码。样本日志:

2017-06-21 11:16:41+05:30 Fetching information about files in the directory...

在脚本顶部添加以下行:

LOG_FILE=script.log
exec > >(while read -r line; do printf '%s %s\n' "$(date --rfc-3339=seconds)" "$line" | tee -a $LOG_FILE; done)
exec 2> >(while read -r line; do printf '%s %s\n' "$(date --rfc-3339=seconds)" "$line" | tee -a $LOG_FILE; done >&2)

希望这对某人有帮助!


是否可以记录所有的应用程序错误登录到主机目录,以便它将有助于DEVOPS ....
普拉萨德欣德

3

对于日志文件,您可能需要输入日期才能输入文本数据。以下代码可能会有所帮助

# declaring variables

Logfile="logfile.txt"   
MAIL_LOG="Message to print in log file"  
Location="were is u want to store log file"

cd $Location   
if [ -f $Logfile ]  
then   
echo "$MAIL_LOG " >> $Logfile

else        

touch $Logfile   
echo "$MAIL_LOG" >> $Logfile    

fi  

输出:2.日志文件将在第一次运行时创建,并在以后的运行中不断更新。如果将来运行时缺少日志文件,脚本将创建新的日志文件。


1

我找到了一种获取所需输出的方法。虽然这可能是一些非常规的方法。无论如何,它去了。在redir.env文件中,我有以下代码:

#####redir.env#####    
export LOG_FILE=log.txt

      exec 2>>${LOG_FILE}

    function log {
     echo "$1">>${LOG_FILE}
    }

    function message {
     echo "$1"
     echo "$1">>${LOG_FILE}
    }

然后在实际脚本中,我有以下代码:

#!/bin/sh 
. redir.env
echo "Echoed to console only"
log "Written to log file only"
message "To console and log"
echo "This is stderr. Written to log file only" 1>&2

在此,echo输出仅输出到控制台,日志输出仅输出到日志文件,消息输出同时输出到日志文件和控制台。

执行完上面的脚本文件后,我有以下输出:

在控制台中

在控制台中
仅响应控制台进行
控制台和登录

对于日志文件

在“ 仅写入日志文件的日志文件”中,
这是stderr。只写日志文件
控制台和日志

希望能有所帮助。


1

试试这个,它将完成工作:

log_file=$curr_dir/log_file.txt
exec > >(tee -a ${log_file} )
exec 2> >(tee -a ${log_file} >&2)

exec > >(tee -a ${log_file} )非常适合我的需求。上面的先前解决方案将中断脚本的某些部分,如果它们失败则将强制退出。谢谢
MitchellK

1
    #
    #------------------------------------------------------------------------------
    # echo pass params and print them to a log file and terminal
    # with timestamp and $host_name and $0 PID
    # usage:
    # doLog "INFO some info message"
    # doLog "DEBUG some debug message"
    # doLog "WARN some warning message"
    # doLog "ERROR some really ERROR message"
    # doLog "FATAL some really fatal message"
    #------------------------------------------------------------------------------
    doLog(){
        type_of_msg=$(echo $*|cut -d" " -f1)
        msg=$(echo "$*"|cut -d" " -f2-)
        [[ $type_of_msg == DEBUG ]] && [[ $do_print_debug_msgs -ne 1 ]] && return
        [[ $type_of_msg == INFO ]] && type_of_msg="INFO " # one space for aligning
        [[ $type_of_msg == WARN ]] && type_of_msg="WARN " # as well

        # print to the terminal if we have one
        test -t 1 && echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][@$host_name] [$$] ""$msg"

        # define default log file none specified in cnf file
        test -z $log_file && \
            mkdir -p $product_instance_dir/dat/log/bash && \
                log_file="$product_instance_dir/dat/log/bash/$run_unit.`date "+%Y%m"`.log"
        echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][@$host_name] [$$] ""$msg" >> $log_file
    }
    #eof func doLog

0

我发现将stdout和stderr都附加到日志文件非常有用。我很高兴看到alfonx的解决方案exec > >(tee -a),因为我想知道如何使用来完成此任务exec。我遇到了一个使用here-doc语法和.以下内容的创造性解决方案:https : //unix.stackexchange.com/questions/80707/how-to-output-text-to-both-screen-and-file-inside-a-shell -脚本

我发现在zsh中,可以使用“ multios”构造修改here-doc解决方案,以将输出复制到stdout / stderr和日志文件中:

#!/bin/zsh
LOG=$0.log
# 8 is an arbitrary number;
# multiple redirects for the same file descriptor 
# triggers "multios"
. 8<<\EOF /dev/fd/8 2>&2 >&1 2>>$LOG >>$LOG
# some commands
date >&2
set -x
echo hi
echo bye
EOF
echo not logged

它不像exec解决方案那样可读,但是它具有允许您仅记录脚本一部分的优点。当然,如果省略EOF,则整个脚本将通过日志记录执行。我不确定如何zsh实现multios,但开销可能比少tee。不幸的是,似乎不能将multios与一起使用exec

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.