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

128
在Unix shell中,我有一个env文件(env文件定义了运行用户脚本所需的参数,比如日志文件名和路径,将输出和错误重定向到日志文件,数据库连接细节等),使用以下代码将所有输出(echo消息)和错误从执行的脚本重定向到日志文件:
exec 1>>${LOG_FILE}
exec 2>>${LOG_FILE}

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

但有一些我想要在控制台和日志文件中显示的选择性用户输出。但因为上面的代码,我不能这样做。

我知道如果我删除上述代码,我可以得到这种情况下想要的结果,但我必须手动将所有其他输出写入日志文件,这不是一项容易的任务。

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

9个回答

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

此命令将标准输出和错误输出发送到日志文件中,但会保留连接到控制台的第三个文件描述符,以便您可以执行以下操作:

echo "Some console message" 1>&3

要将消息仅写入控制台,请使用以下命令:

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

使用tee命令可以将输出同时发送到控制台和日志文件中。它会将输出发送到自己的文件描述符1(在这里是LOG_FILE),以及你指定要写入的文件(在这里是文件描述符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
@shrestha 这是tee的一个不寻常的用法,我同意。 /dev/fd/3 是一个文件名,它指的是“当前打开的fd 3”,因此,tee /dev/fd/3 将其stdin接收到的任何内容写入fd 1和fd 3(即 /dev/fd/3 文件)。Fd 1连接到日志文件,fd 3连接到控制台。 - Ian Roberts
如果您只想将内容写入文件而不是控制台,请尝试以下命令:echo "blah" | tee file1.txt | tee file2.txt >/dev/null这样就可以把“blah”写入file1.txt和file2.txt,但不会在控制台输出。 - Melroy van den Berg
这对我非常有帮助。虽然我注意到可能有些离题,但也许你们中的一些人知道原因。我正在从bash脚本执行R脚本。各种R函数的控制台输出是彩色的(如我所定义)。当使用上述方法将输出重定向到控制台和日志文件时,控制台输出不再是彩色的。这可能是什么原因? - dieHellste
1
@dieHellste 一些程序能够检测到它们的输出是否被管道传输到另一个进程(在这种情况下是 tee,然后写入终端),而不是直接传输到终端,并调整它们的输出以匹配。 - Ian Roberts
显示剩余2条评论

53

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

exec: 1: not found

错误。下面是对我而言最有效的方法(已确认在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 

如果你不想让/tmp/both.log被追加,请在tee命令中去掉-a参数。

提示:>(...)是一种进程替换。它允许将exec的输出作为文件传递给tee命令。


2
这对我来说非常有效,而其他答案则有时有错有对。 - Jay Taylor
感谢分享这段代码片段。与其他答案相比,它似乎运行得非常好! - tftd
这个可以运行,但是执行后我的 shell 变了。 - Josh Usre
在RHEL 7中使用Bash。执行后,我的终端颜色消失了,当我启动vi时,它会抛出一个警告,说我没有从终端运行它。可能是我复制错了或者其他什么问题,但我确保完全按照原样使用。请问还需要提供什么信息(请详细说明如何提供,因为我不是认证管理员或任何相关人员)? - Josh Usre
2
如果您不需要区分标准输出和错误输出,您可以将语句组合为 exec > >(tee ${LOG_FILE}) 2>&1 - darkdragon
显示剩余2条评论

46

是的,你想使用tee:

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

只需将命令通过管道传输到tee,并将文件作为参数传递,像这样:

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

这将同时输出内容到标准输出和写入同样的内容到日志文件中。更多信息请参见man tee

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

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

3
以上解决方案不起作用。我有一个redirect.env文件,其中包含以下内容:#####redirect.env###### export LOG_FILE=log.txt exec 1 2>&1 | tee -a ${LOG_FILE} exec 1 | tee -a ${LOG_FILE} exec 2 | tee -a ${LOG_FILE} #########工作文件包含以下代码:#####output.sh##### #!/bin/sh . redirect.envecho "Valid output" ech "invalid output" ##############但是我收到以下错误:

redirect.env: 行 3: exec: 1: 未找到命令 redirect.env: 行 5: exec: 1: 未找到命令 redirect.env: 行 6: exec: 2: 未找到命令 ####在日志文件中也得到了同样的错误。我做错了什么吗?
- abinash shrestha
很难确定,因为您的评论中删除了新行。您能否在gist之类的地方添加代码粘贴? - Jon Cairns
1
我已将文件添加到链接UnixRedirect中。 相关文件是redirect.env和output.sh。 - abinash shrestha
2
这段代码似乎无法工作(至少不再起作用了)。通常会出现 script.sh: line 5: exec: 1: not found 的错误提示。 - tftd

9
我希望能够在标准输出和日志文件中显示带有时间戳的日志。以上回答都不能满足我的需求。我使用了"进程替换"和"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? - Prasad Shinde

5

如果您想要将日志记录到文本数据中,可以使用以下代码:

# 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.第一次运行时会创建日志文件并且从下一次运行开始不断更新。如果未来的运行中缺少日志文件,脚本将创建新的日志文件。


3

试试这个,它会起作用:

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

2
    #
    #------------------------------------------------------------------------------
    # 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

2
我找到了一种方法来获取所需的输出。尽管这种方法可能有些不正规,但还是有用的。在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 只输出到控制台,log 只输出到日志文件,而 message 则同时输出到控制台和日志文件。

执行以上脚本后,我得到以下输出:

在控制台中

在控制台中
仅在控制台中输出
同时输出到控制台和日志文件

对于日志文件

在日志文件中
仅在日志文件中写入
这是 stderr。仅在日志文件中写入
同时输出到控制台和日志文件

希望这可以帮到你。


我很欣赏这个解决方案。相比其他一些方案,它更为简单明了。 - shawn1874

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-script

我发现在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的开销小。不幸的是,似乎不能在exec中使用multios。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接