【问题标题】:Writing outputs to log file and console将输出写入日志文件和控制台
【发布时间】:2013-08-29 21:22:36
【问题描述】:

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

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

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

但是我希望在控制台和日志文件中都显示一些选择性的用户输出。但是由于上面的代码,我无法这样做。

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

有没有办法在不删除上述代码的情况下同时在控制台和日志文件中获取输出?

【问题讨论】:

    标签: bash shell redirect logging


    【解决方案1】:
    exec 3>&1 1>>${LOG_FILE} 2>&1
    

    会将标准输出和标准错误输出发送到日志文件中,但也会让 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
    

    进入日志文件。

    【讨论】:

    • 它按照您的建议工作。但我不明白 tee /dev/fd/3。我知道 tee 将消息写入日志文件和控制台,但我并不完全理解 tee 之后使用的 /dev/fd/3
    • @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,但不会写入控制台。
    • 这对我很有帮助。虽然我注意到一些可能离题的事情,但也许你们中的一些人知道其中的原因。我从 bash 脚本执行 R 脚本。各种 R 函数的控制台输出是彩色的(正如我定义的那样)。当使用上述方法将输出重定向到控制台和日志文件时,控制台输出不再着色。这可能是什么原因?
    • @dieHellste 一些程序能够检测到它们的输出何时通过管道传输到另一个进程(在本例中为 tee,它依次写入终端)而不是直接进入终端,并且调整它们的输出以匹配。
    【解决方案2】:

    是的,你想使用tee

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

    只需将您的命令通过管道传递给 tee 并将文件作为参数传递,如下所示:

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

    这会将输出打印到 STDOUT 并将相同的输出写入日志文件。请参阅man tee 了解更多信息。

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

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

    【讨论】:

    • 上述解决方案无效。我有 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 "invalid output" ############## 但我得到错误:#### redirect.env: line 3: exec: 1: not found redirect.env:第 5 行:exec:1:未找到 redirect.env:第 6 行:exec:2:未找到 #### 并且在日志文件中我也收到相同的错误。我做错什么了吗?
    • 很难说,因为新行在您的评论中被删除了。你能把代码粘贴到gist这样的地方吗?
    • 我已将文件添加到链接UnixRedirect。相关文件是redirect.env和output.sh
    • 这段代码似乎不起作用(至少不再起作用)。你通常会得到script.sh: line 5: exec: 1: not found
    【解决方案3】:

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

    执行:1:未找到

    错误。这对我最有效(confirmed 也可以在 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 命令就像一个文件一样。

    【讨论】:

    • 这对我来说就像是一种魅力,而其他答案却被击中或错过。
    • 感谢分享这个 sn-p。这似乎工作得很好(与其他答案相比)!
    • 这可行,但现在我的shell在执行后有所不同。
    • @JoshUsre 如果您需要任何帮助或想帮助其他 SO 用户,请说明有什么不同,并描述您的外壳、版本、操作系统等。
    • 如果不需要区分stdout和stderr,可以合并语句到exec > >(tee ${LOG_FILE}) 2>&1
    【解决方案4】:

    我想在标准输出和日志文件上显示日志以及时间戳。以上答案都不适合我。 我使用了 process substitutionexec 命令并想出了以下代码。 示例日志:

    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)
    

    希望这对某人有所帮助!

    【讨论】:

    • 是否可以记录所有应用程序错误以登录到主机目录,以便帮助开发人员......
    【解决方案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. 日志文件将在第一次运行时创建,并在下次运行时继续更新。如果以后运行日志文件丢失,脚本将创建新的日志文件。

    【讨论】:

      【解决方案6】:

      试试这个,它会做的工作:

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

      【讨论】:

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

      我找到了一种获得所需输出的方法。虽然这可能有点非正统的方式。无论如何,它去。在 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输出到日志文件和控制台。 p>

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

      在控制台中

      在控制台中
      仅回显到控制台
      控制台和日志

      对于日志文件

      在日志文件中 仅写入日志文件
      这是标准错误。仅写入日志文件
      控制台和日志

      希望对您有所帮助。

      【讨论】:

        【解决方案8】:
            #
            #------------------------------------------------------------------------------
            # 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
        

        【讨论】:

          【解决方案9】:

          我发现将 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 是如何实现多操作系统的,但它的开销可能比tee 少。不幸的是,exec 似乎不能使用 multios。

          【讨论】:

            最近更新 更多