【问题标题】:Logging stderr and stdout to log file and handling errors in bash script [duplicate]将stderr和stdout记录到日志文件并处理bash脚本中的错误[重复]
【发布时间】:2015-06-26 10:36:29
【问题描述】:

解决方案:

Send stderr/stdout messages to function and trap exit signal

编辑 我发现我在原始帖子中不够精确,对此感到抱歉!因此,现在我将包含我的 bash 脚本的代码示例,其中包含一些精致的问题:

我的四个问题:

  1. 如何在 STEP 中的 mysql 命令中发送 stdoutfilestderrlog() 函数2
  2. 如何将 stdoutstderr 发送到 gzip STEP 2 和 rsync 命令 STEP 3 中的 log() 函数?
  3. 如何读取上述问题1和问题2中log()函数中的stdoutstderr
  4. 如何在 onexit() 函数中仍然捕获所有错误?

通用:我希望 stdout 和 stderr 转到 log() 函数,以便可以根据特定格式将消息放入特定日志文件,但在某些情况下,例如 mysqldump 命令,标准输出需要去文件。另外,发生错误并发送到 stderr,我想在 log 语句完成后结束 onexit() 函数。

#!/bin/bash -Eu
# -E: ERR trap is inherited by shell functions.
# -u: Treat unset variables as an error when substituting.

# Example script for handling bash errors.  Exit on error.  Trap exit.
# This script is supposed to run in a subshell.
# See also: http://fvue.nl/wiki/Bash:_Error_handling

#  Trap non-normal exit signals: 2/INT, 3/QUIT, 15/TERM,
trap onexit 1 2 3 15 ERR

# ****** VARIABLES STARTS HERE ***********
BACKUP_ROOT_DIR=/var/app/backup
BACKUP_DIR_DATE=${BACKUP_ROOT_DIR}/`date "+%Y-%m-%d"`
EMAIL_FROM="foo@bar.com"
EMAIL_RECIPIENTS="bar@foo.com"
...
# ****** VARIABLES STARTS HERE ***********

# ****** FUNCTIONS STARTS HERE ***********
# Function that checks if all folders exists and create new ones as required
function checkFolders()
{
    if [ ! -d "${BACKUP_ROOT_DIR}" ] ; then
        log "ERROR" "Backup directory doesn't exist"
        exit
    else
        log "INFO" "All folders exists"
    fi

    if [ ! -d "${BACKUP_DIR_DATE}" ] ; then
        mkdir ${BACKUP_DIR_DATE} -v
        log "INFO" "Created new backup directory"
    else
        log "WARN" "Backup directory already exists"
    fi
}

# Function executed when exiting the script, either because of an error or successfully run
function onexit() {
    local exit_status=${1:-$?}

    # Send email notification with the status
    echo "Backup finished at `date` with status ${exit_status_text} | mail -s "${exit_status_text} - backup" -S from="${EMAIL_FROM}" ${EMAIL_RECIPIENTS}"
    log "INFO" "Email notification sent with execution status ${exit_status_text}"
   
    # Print script duration to the console
    ELAPSED_TIME=$((${SECONDS} - ${START_TIME}))
    log "INFO" "Backup finished" "startDate=\"${START_DATE}\", endDate=\"`date`\", duration=\"$((${ELAPSED_TIME}/60)) min $((${ELAPSED_TIME}%60)) sec\""
    
    exit ${exit_status}
}

# Logs to custom log file according to preferred log format for Splunk
# Input:
#   1. severity (INFO,WARN,DEBUG,ERROR)
#   2. the message
#   3. additional fields
#
function log() {
    local print_msg="`date +"%FT%T.%N%Z"` severity=\"${1}\",message=\"${2}\",transactionID=\"${TRANS_ID}\",source=\"${SCRIPT_NAME}\",environment=\"${ENV}\",application=\"${APP}\""

    # check if additional fields set in the 3. parameter
    if [ $# -eq 3 ] ; then
        print_msg="${print_msg}, ${3}"
    fi

    echo ${print_msg} >> ${LOG_FILE}
}
# ****** FUNCTIONS ENDS HERE ***********

# ****** SCRIPT STARTS HERE ***********
log "INFO" "Backup of ${APP} in ${ENV} starting"

# STEP 1 - validate
log "INFO" "1/3 Checking folder paths"
checkFolders

# STEP 2 - mysql dump
log "INFO" "2/3 Dumping ${APP} database"
mysqldump --single-transaction ${DB_NAME} > ${BACKUP_DIR_DATE}/${SQL_BACKUP_FILE}
gzip -f ${BACKUP_DIR_DATE}/${SQL_BACKUP_FILE}
log "INFO" "Mysql dump finished."

# STEP 3 - transfer
# Files are only transferred if all commands has been running successfully. Transfer is done with use of rsync
log "INFO" "3/3 Transferring backup file"
rsync -r -av ${BACKUP_ROOT_DIR}/ ${BACKUP_TRANSFER_USER}@${BACKUP_TRANSFER_DEST}

# ****** SCRIPT ENDS HERE ***********
onexit

谢谢!

【问题讨论】:

    标签: bash shell unix stderr


    【解决方案1】:

    试试这个:

    mylogger() { printf "Log: %s\n" "$(</dev/stdin)"; }
    
    mysqldump ... 2>&1 >dumpfilename.sql | mylogger
    

    【讨论】:

      【解决方案2】:

      Cyrus's answerOleg Vaskevich's answer 都为将 stderr 重定向到 shell 函数提供了可行的解决方案。

      他们都暗示你的函数接受stdin输入而不是期望输入作为参数是有意义的。

      解释他们使用的成语:

      mysqldump ... 2>&1 > sdtout-file | log-func-that-receives-stderr-via-stdin
      
      • 2&gt;&amp;1 ... 将标准错误重定向到 original 标准输出
        • 从那时起,任何标准错误输入都被重定向到标准输出
      • &gt; sdtout-file then 将原始 stdout 重定向到文件 stdout-out-file
        • 从那时起,任何标准输出输入都将重定向到文件。

      由于&gt; stdout-file 出现在 2&gt;&amp;1 之后,最终结果是:

      • stdout 输出重定向到文件stdout-file
      • stderr 输出被发送到 [原始] 标准输出

      因此,log-func-that-receives-stderr-via-stdin 仅通过管道接收前一个命令的 stderr 输入,通过 its stdin

      同样,您的原始方法 - command 2&gt; &gt;(logFunction) - 在原理 中起作用,但要求您的 log() 函数从 stdin 读取而不是期望 参数

      以下说明原理:

      ls / nosuchfile 2> >(sed s'/^/Log: /') > stdout-file
      
      • ls / nosuchfile 产生stdout 和stderr 输出。
      • stdout 输出被重定向到文件stdout-file
      • 2&gt; &gt;(...) 使用 [output] process substitution 将 stderr 输出重定向到 &gt;(...) 中包含的命令 - 该命令通过 其标准输入接收输入。
        • sed s'/^/Log: /' 从其标准输入中读取,并将字符串 Log: 添加到每个输入行。

      因此,您的log() 函数应该被重写以处理stdin

      • 任一:通过将输入隐式传递给另一个标准输入处理实用程序,例如 sedawk(如上)。
      • :通过使用while read ... 循环来处理shell 循环中的每个输入行:
      log() {
        # `read` reads from stdin by default
        while IFS= read -r line; do
          printf 'STDERR line: %s\n' "$line"
        done
      }
      
      mysqldump ... 2> >(log) > stdout-file
      

      【讨论】:

        【解决方案3】:

        假设您的日志函数如下所示(它只是echos 第一个参数):

        log() { echo "$1"; }
        

        要将 mysqldump 的 stdout 保存到某个文件并为 stderr 中的每一行调用 log() 函数,请执行以下操作:

        mysqldump 2>&1 >/your/sql_dump_file.dat | while IFS= read -r line; do log "$line"; done
        

        如果你想使用 xargs,你可以这样做。但是,您每次都会启动一个新的 shell。

        export -f log
        mysqldump 2>&1 >/your/sql_dump_file.dat | xargs -L1 bash -i -c 'log $@' _
        

        【讨论】:

        • 来自mysqldump的stdout需要写入dump.sql文件,所以我不能将stderr重定向到stdout。请问,第一条语句将stderr存入log.sh文件?
        • 这:mysqldump 2&gt;&amp;1 | ./log.sh 将 mysqldump 的 stderr 和 stdout 输出通过管道传输到名为 log.sh 的 Bash 脚本。
        • 好的,抱歉。现在我看到了。但是真的需要为此创建 shell 脚本吗?
        • @mklement0 谢谢 - 忘了那些双引号。不知道 xargs 不适用于函数,但这是有道理的。我想你需要先导出函数并在一个新的 shell 中启动它(这可能太重了)。或者,您可以使用 while 循环。
        • :) +?; quibble:使用IFS= read -r line 读取输入行未修改(否则,前导和尾随空白被修剪)。
        猜你喜欢
        • 1970-01-01
        • 2013-06-09
        • 2022-10-15
        • 1970-01-01
        • 1970-01-01
        • 2011-07-16
        • 2021-08-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多