将 stderr 和 stdout 记录到日志文件并处理 bash 脚本中的错误
Logging stderr and stdout to log file and handling errors in bash script
解决方案:
见
编辑
我发现我的原文 post 不够精确,对此感到抱歉!所以现在我将包含我的 bash 脚本的代码示例,其中包含改进的问题:
我的四个问题:
- 如何将 stdout 发送到 file 并将 stderr 发送到 STEP 2
mysql 命令中的 log() 函数
- 如何将 stdout 和 stderr 发送到 gzip STEP 中的 log() 函数2 和 rsync 命令步骤 3?
- 如何读取上面问题1和问题2中log()函数中的stdout和stderr?
- 我怎样才能在 onexit() 函数中捕获所有错误?
一般:我希望 stdout 和 stderr 转到 log() 函数,以便可以根据特定格式将消息放入特定日志文件,但在某些情况下,像 mysqldump 命令一样,stdout 需要转到文件。此外,发生错误并发送到 stderr,我想在日志语句完成后在 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=\"\",message=\"\",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}, "
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
谢谢!
假设您的日志函数如下所示(它只是 echo
第一个参数):
log() { echo ""; }
要将 mysqldump 的标准输出保存到某个文件并为标准错误中的每一行调用 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 $@' _
试试这个:
mylogger() { printf "Log: %s\n" "$(</dev/stdin)"; }
mysqldump ... 2>&1 >dumpfilename.sql | mylogger
两者 and 都提供了将 stderr 重定向到 shell 函数的可行解决方案。
它们都暗示 您的函数接受 stdin 输入而不是期望输入 作为参数是有意义的.
解释他们使用的习语:
mysqldump ... 2>&1 > sdtout-file | log-func-that-receives-stderr-via-stdin
2>&1
... 将 stderr 重定向到 original stdout
- 从那时起,任何 stderr 输入都被重定向到 stdout
> sdtout-file
然后 将原始 stdout stdout 重定向到文件 stdout-out-file
- 从那时起,任何标准输出输入都将重定向到该文件。
由于 > stdout-file
在 2>&1
之后 ,最终结果是:
- stdout 输出被重定向到文件
stdout-file
- stderr 输出发送到[原始] stdout
因此,log-func-that-receives-stderr-via-stdin
仅通过管道接收前一个命令的 stderr 输入,通过 its 标准输入.
类似地,您的原始方法 - command 2> >(logFunction)
- 在 原则 中工作,但需要您的 log()
函数从 stdin 而不是期望 arguments:
下面说明原理:
ls / nosuchfile 2> >(sed s'/^/Log: /') > stdout-file
ls / nosuchfile
产生 stdout 和 stderr 输出。
- stdout 输出被重定向到文件
stdout-file
.
2> >(...)
使用 [output] process substitution 将 stderr 输出重定向到包含在 >(...)
中的命令 - 该命令通过 接收输入它的标准输入。
sed s'/^/Log: /'
从其标准输入中读取并将字符串 Log:
添加到每个输入行。
因此,您的 log()
函数应该被重写以处理 stdin:
- either:通过将输入隐式传递给另一个标准输入处理实用程序,例如
sed
或 awk
(如上)。
- 或:通过使用
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
解决方案:
见
编辑 我发现我的原文 post 不够精确,对此感到抱歉!所以现在我将包含我的 bash 脚本的代码示例,其中包含改进的问题:
我的四个问题:
- 如何将 stdout 发送到 file 并将 stderr 发送到 STEP 2 mysql 命令中的 log() 函数
- 如何将 stdout 和 stderr 发送到 gzip STEP 中的 log() 函数2 和 rsync 命令步骤 3?
- 如何读取上面问题1和问题2中log()函数中的stdout和stderr?
- 我怎样才能在 onexit() 函数中捕获所有错误?
一般:我希望 stdout 和 stderr 转到 log() 函数,以便可以根据特定格式将消息放入特定日志文件,但在某些情况下,像 mysqldump 命令一样,stdout 需要转到文件。此外,发生错误并发送到 stderr,我想在日志语句完成后在 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=\"\",message=\"\",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}, "
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
谢谢!
假设您的日志函数如下所示(它只是 echo
第一个参数):
log() { echo ""; }
要将 mysqldump 的标准输出保存到某个文件并为标准错误中的每一行调用 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 $@' _
试试这个:
mylogger() { printf "Log: %s\n" "$(</dev/stdin)"; }
mysqldump ... 2>&1 >dumpfilename.sql | mylogger
两者
它们都暗示 您的函数接受 stdin 输入而不是期望输入 作为参数是有意义的.
解释他们使用的习语:
mysqldump ... 2>&1 > sdtout-file | log-func-that-receives-stderr-via-stdin
2>&1
... 将 stderr 重定向到 original stdout- 从那时起,任何 stderr 输入都被重定向到 stdout
> sdtout-file
然后 将原始 stdout stdout 重定向到文件stdout-out-file
- 从那时起,任何标准输出输入都将重定向到该文件。
由于 > stdout-file
在 2>&1
之后 ,最终结果是:
- stdout 输出被重定向到文件
stdout-file
- stderr 输出发送到[原始] stdout
因此,log-func-that-receives-stderr-via-stdin
仅通过管道接收前一个命令的 stderr 输入,通过 its 标准输入.
类似地,您的原始方法 - command 2> >(logFunction)
- 在 原则 中工作,但需要您的 log()
函数从 stdin 而不是期望 arguments:
下面说明原理:
ls / nosuchfile 2> >(sed s'/^/Log: /') > stdout-file
ls / nosuchfile
产生 stdout 和 stderr 输出。- stdout 输出被重定向到文件
stdout-file
. 2> >(...)
使用 [output] process substitution 将 stderr 输出重定向到包含在>(...)
中的命令 - 该命令通过 接收输入它的标准输入。sed s'/^/Log: /'
从其标准输入中读取并将字符串Log:
添加到每个输入行。
因此,您的 log()
函数应该被重写以处理 stdin:
- either:通过将输入隐式传递给另一个标准输入处理实用程序,例如
sed
或awk
(如上)。 - 或:通过使用
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