Bash中的错误处理
您最喜欢用什么方法来处理Bash中的错误? 我在网络上发现的处理错误的最好例子是由William Shotts,Jr 他建议在Bash中使用以下函数进行错误处理:Bash中的错误处理,bash,error-handling,scripting,error-logging,Bash,Error Handling,Scripting,Error Logging,您最喜欢用什么方法来处理Bash中的错误? 我在网络上发现的处理错误的最好例子是由William Shotts,Jr 他建议在Bash中使用以下函数进行错误处理: #!/bin/bash # A slicker error handling routine # I put a variable in my scripts named PROGNAME which # holds the name of the program being run. You can get this # va
#!/bin/bash
# A slicker error handling routine
# I put a variable in my scripts named PROGNAME which
# holds the name of the program being run. You can get this
# value from the first item on the command line ($0).
# Reference: This was copied from <http://www.linuxcommand.org/wss0150.php>
PROGNAME=$(basename $0)
function error_exit
{
# ----------------------------------------------------------------
# Function for exit due to fatal program error
# Accepts 1 argument:
# string containing descriptive error message
# ----------------------------------------------------------------
echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
exit 1
}
# Example call of the error_exit function. Note the inclusion
# of the LINENO environment variable. It contains the current
# line number.
echo "Example of error with line number and message"
error_exit "$LINENO: An error has occurred."
#/bin/bash
#更灵活的错误处理例程
#我在脚本中放了一个名为PROGNAME的变量
#保存正在运行的程序的名称。你可以得到这个
#命令行上第一项的值($0)。
#参考:这是从
PROGNAME=$(basename$0)
函数错误\u退出
{
# ----------------------------------------------------------------
#由于致命程序错误而退出的函数
#接受1个参数:
#包含描述性错误消息的字符串
# ----------------------------------------------------------------
echo“${PROGNAME}:${1:-“未知错误”}”1>&2
出口1
}
#error_exit函数的示例调用。请注意包含的内容
#LINENO环境变量的。它包含当前的
#行号。
echo“行号和消息错误示例”
错误\u退出“$LINENO:发生错误。”
您有更好的错误处理例程用于Bash脚本吗?我用过
die() {
echo $1
kill $$
}
以前;我想因为某种原因,“退出”对我来说是失败的。不过,上面的默认设置似乎是个好主意。这是一个很好的解决方案。我只是想补充一下
set -e
作为一种基本的错误机制。如果简单命令失败,它将立即停止脚本。我认为这应该是默认的行为:因为这样的错误几乎总是意味着一些意想不到的事情,所以继续执行下面的命令并不是真正的“明智的”。另一个考虑因素是返回的退出代码。仅仅“
1
”是相当标准的,尽管有一些,并且同一页主张用户定义的代码应该在64-113范围内,以符合C/C++标准
您还可以考虑“代码> Road < /COD>用于其退出代码的位向量方法:
0 success
1 incorrect invocation or permissions
2 system error (out of memory, cannot fork, no more loop devices)
4 internal mount bug or missing nfs support in mount
8 user interrupt
16 problems writing or locking /etc/mtab
32 mount failure
64 some mount succeeded
或
-将代码放在一起可以让脚本同时发出多个错误的信号。使用陷阱
tempfiles=( )
cleanup() {
rm -f "${tempfiles[@]}"
}
trap cleanup 0
error() {
local parent_lineno="$1"
local message="$2"
local code="${3:-1}"
if [[ -n "$message" ]] ; then
echo "Error on or near line ${parent_lineno}: ${message}; exiting with status ${code}"
else
echo "Error on or near line ${parent_lineno}; exiting with status ${code}"
fi
exit "${code}"
}
trap 'error ${LINENO}' ERR
…然后,无论何时创建临时文件:
temp_foo="$(mktemp -t foobar.XXXXXX)"
tempfiles+=( "$temp_foo" )
退出时将删除$temp_foo
,并打印当前行号。(set-e
同样会让您退出错误行为,并削弱代码的可预测性和可移植性)
您可以让陷阱为您调用error
(在这种情况下,它使用默认的退出代码1,没有消息),也可以自己调用它并提供显式值;例如:
error ${LINENO} "the foobar failed" 2
将以状态2退出,并给出一条明确的消息。我更喜欢一些真正容易调用的东西。所以我用的东西看起来有点复杂,但很容易使用。我通常只是将下面的代码复制并粘贴到脚本中。代码后面有一个解释
#This function is used to cleanly exit any script. It does this displaying a
# given error message, and exiting with an error code.
function error_exit {
echo
echo "$@"
exit 1
}
#Trap the killer signals so that we can exit with a good message.
trap "error_exit 'Received signal SIGHUP'" SIGHUP
trap "error_exit 'Received signal SIGINT'" SIGINT
trap "error_exit 'Received signal SIGTERM'" SIGTERM
#Alias the function so that it will print a message with the following format:
#prog-name(@line#): message
#We have to explicitly allow aliases, we do this because they make calling the
#function much easier (see example).
shopt -s expand_aliases
alias die='error_exit "Error ${0}(@`echo $(( $LINENO - 1 ))`):"'
我通常在error_exit函数中调用cleanup函数,但这因脚本而异,所以我将其忽略。陷阱捕捉常见的终端信号,并确保所有东西都被清理干净。别名才是真正的魔力所在。我喜欢检查每件事是否失败。所以一般来说,我用“if!”类型的语句调用程序。通过从行号中减去1,别名将告诉我故障发生的位置。它也非常容易调用,而且几乎是傻瓜式的。下面是一个示例(只需将/bin/false替换为您要调用的内容)
“set-e”的一个等效替代方案是 它使国旗的含义比“e”更清楚一些 随机添加:要临时禁用该标志,并返回默认值(无论退出代码如何继续执行),只需使用
set +e
echo "commands run here returning non-zero exit codes will not cause the entire script to fail"
echo "false returns 1 as an exit code"
false
set -e
这排除了其他响应中提到的正确的错误处理,但它是快速有效的(就像bash一样)。已经为我提供了一段时间了。它以红色打印错误或警告消息,每个参数一行,并允许可选的退出代码
# Custom errors
EX_UNKNOWN=1
warning()
{
# Output warning messages
# Color the output red if it's an interactive terminal
# @param $1...: Messages
test -t 1 && tput setf 4
printf '%s\n' "$@" >&2
test -t 1 && tput sgr0 # Reset terminal
true
}
error()
{
# Output error messages with optional exit code
# @param $1...: Messages
# @param $N: Exit code (optional)
messages=( "$@" )
# If the last parameter is a number, it's not part of the messages
last_parameter="${messages[@]: -1}"
if [[ "$last_parameter" =~ ^[0-9]*$ ]]
then
exit_code=$last_parameter
unset messages[$((${#messages[@]} - 1))]
fi
warning "${messages[@]}"
exit ${exit_code:-$EX_UNKNOWN}
}
我使用以下陷阱代码,它还允许通过管道和“时间”命令跟踪错误
#!/bin/bash
set -o pipefail # trace ERR through pipes
set -o errtrace # trace ERR through 'time command' and other functions
function error() {
JOB="$0" # job name
LASTLINE="$1" # line of error occurrence
LASTERR="$2" # error code
echo "ERROR in ${JOB} : line ${LASTLINE} with exit code ${LASTERR}"
exit 1
}
trap 'error ${LINENO} ${?}' ERR
不确定这是否对您有帮助,但我修改了一些建议的函数,以便在其中包含错误检查(先前命令的退出代码)。 在每次“检查”时,我还将错误的“消息”作为参数传递,以便于记录
#!/bin/bash
error_exit()
{
if [ "$?" != "0" ]; then
log.sh "$1"
exit 1
fi
}
现在,要在同一个脚本中调用它(如果我使用export-f error\u exit
,则在另一个脚本中调用),我只需编写函数名并将消息作为参数传递,如下所示:
#!/bin/bash
cd /home/myuser/afolder
error_exit "Unable to switch to folder"
rm *
error_exit "Unable to delete all files"
使用此功能,我能够为一些自动化过程创建一个非常健壮的bash文件,它会在出现错误时停止并通知我(
log.sh
会这样做)阅读此页面上的所有答案给了我很多启发。所以,这里是我的提示:
文件内容:lib.trap.sh
运行: 输出:
doing something wrong now ..
(!) EXIT HANDLER:
FILE: trap-test.sh
LINE: 6
ERROR CODE: 1
ERROR MESSAGE:
foo: unassigned variable
BACKTRACE IS:
1 main trap-test.sh
Exiting!
从下面的屏幕截图中可以看到,输出是彩色的,错误消息以使用的语言显示。
对于缺少的命令或函数,此技巧非常有用。缺少的函数(或可执行文件)的名称将以$形式传递
function handle_error {
status=$?
last_call=$1
# 127 is 'command not found'
(( status != 127 )) && return
echo "you tried to call $last_call"
return
}
# Trap errors.
trap 'handle_error "$_"' ERR
受这里介绍的想法的启发,我开发了一种可读且方便的方法来处理我的应用程序中bash脚本中的错误 通过简单地寻找库,您可以获得以下现成的功能(即,由于
ERR
上的trap
和一些bash-fu,它将在任何错误时停止执行,就像使用set-e
):
有一些额外的功能可以帮助处理错误,例如try and catch,或throw关键字,允许您在某个点中断执行以查看回溯。此外,如果终端支持它,它会输出电力线表情,为输出的部分着色以提高可读性,并在导致错误的方法上加下划线
lib_name='trap'
lib_version=20121026
stderr_log="/dev/shm/stderr.log"
#
# TO BE SOURCED ONLY ONCE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
if test "${g_libs[$lib_name]+_}"; then
return 0
else
if test ${#g_libs[@]} == 0; then
declare -A g_libs
fi
g_libs[$lib_name]=$lib_version
fi
#
# MAIN CODE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
set -o pipefail # trace ERR through pipes
set -o errtrace # trace ERR through 'time command' and other functions
set -o nounset ## set -u : exit the script if you try to use an uninitialised variable
set -o errexit ## set -e : exit the script if any statement returns a non-true return value
exec 2>"$stderr_log"
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: EXIT_HANDLER
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
function exit_handler ()
{
local error_code="$?"
test $error_code == 0 && return;
#
# LOCAL VARIABLES:
# ------------------------------------------------------------------
#
local i=0
local regex=''
local mem=''
local error_file=''
local error_lineno=''
local error_message='unknown'
local lineno=''
#
# PRINT THE HEADER:
# ------------------------------------------------------------------
#
# Color the output if it's an interactive terminal
test -t 1 && tput bold; tput setf 4 ## red bold
echo -e "\n(!) EXIT HANDLER:\n"
#
# GETTING LAST ERROR OCCURRED:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
#
# Read last file from the error log
# ------------------------------------------------------------------
#
if test -f "$stderr_log"
then
stderr=$( tail -n 1 "$stderr_log" )
rm "$stderr_log"
fi
#
# Managing the line to extract information:
# ------------------------------------------------------------------
#
if test -n "$stderr"
then
# Exploding stderr on :
mem="$IFS"
local shrunk_stderr=$( echo "$stderr" | sed 's/\: /\:/g' )
IFS=':'
local stderr_parts=( $shrunk_stderr )
IFS="$mem"
# Storing information on the error
error_file="${stderr_parts[0]}"
error_lineno="${stderr_parts[1]}"
error_message=""
for (( i = 3; i <= ${#stderr_parts[@]}; i++ ))
do
error_message="$error_message "${stderr_parts[$i-1]}": "
done
# Removing last ':' (colon character)
error_message="${error_message%:*}"
# Trim
error_message="$( echo "$error_message" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
fi
#
# GETTING BACKTRACE:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
_backtrace=$( backtrace 2 )
#
# MANAGING THE OUTPUT:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
local lineno=""
regex='^([a-z]{1,}) ([0-9]{1,})$'
if [[ $error_lineno =~ $regex ]]
# The error line was found on the log
# (e.g. type 'ff' without quotes wherever)
# --------------------------------------------------------------
then
local row="${BASH_REMATCH[1]}"
lineno="${BASH_REMATCH[2]}"
echo -e "FILE:\t\t${error_file}"
echo -e "${row^^}:\t\t${lineno}\n"
echo -e "ERROR CODE:\t${error_code}"
test -t 1 && tput setf 6 ## white yellow
echo -e "ERROR MESSAGE:\n$error_message"
else
regex="^${error_file}\$|^${error_file}\s+|\s+${error_file}\s+|\s+${error_file}\$"
if [[ "$_backtrace" =~ $regex ]]
# The file was found on the log but not the error line
# (could not reproduce this case so far)
# ------------------------------------------------------
then
echo -e "FILE:\t\t$error_file"
echo -e "ROW:\t\tunknown\n"
echo -e "ERROR CODE:\t${error_code}"
test -t 1 && tput setf 6 ## white yellow
echo -e "ERROR MESSAGE:\n${stderr}"
# Neither the error line nor the error file was found on the log
# (e.g. type 'cp ffd fdf' without quotes wherever)
# ------------------------------------------------------
else
#
# The error file is the first on backtrace list:
# Exploding backtrace on newlines
mem=$IFS
IFS='
'
#
# Substring: I keep only the carriage return
# (others needed only for tabbing purpose)
IFS=${IFS:0:1}
local lines=( $_backtrace )
IFS=$mem
error_file=""
if test -n "${lines[1]}"
then
array=( ${lines[1]} )
for (( i=2; i<${#array[@]}; i++ ))
do
error_file="$error_file ${array[$i]}"
done
# Trim
error_file="$( echo "$error_file" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
fi
echo -e "FILE:\t\t$error_file"
echo -e "ROW:\t\tunknown\n"
echo -e "ERROR CODE:\t${error_code}"
test -t 1 && tput setf 6 ## white yellow
if test -n "${stderr}"
then
echo -e "ERROR MESSAGE:\n${stderr}"
else
echo -e "ERROR MESSAGE:\n${error_message}"
fi
fi
fi
#
# PRINTING THE BACKTRACE:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
test -t 1 && tput setf 7 ## white bold
echo -e "\n$_backtrace\n"
#
# EXITING:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
test -t 1 && tput setf 4 ## red bold
echo "Exiting!"
test -t 1 && tput sgr0 # Reset terminal
exit "$error_code"
}
trap exit_handler EXIT # ! ! ! TRAP EXIT ! ! !
trap exit ERR # ! ! ! TRAP ERR ! ! !
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: BACKTRACE
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
function backtrace
{
local _start_from_=0
local params=( "$@" )
if (( "${#params[@]}" >= "1" ))
then
_start_from_="$1"
fi
local i=0
local first=false
while caller $i > /dev/null
do
if test -n "$_start_from_" && (( "$i" + 1 >= "$_start_from_" ))
then
if test "$first" == false
then
echo "BACKTRACE IS:"
first=true
fi
caller $i
fi
let "i=i+1"
done
}
return 0
#!/bin/bash
source 'lib.trap.sh'
echo "doing something wrong now .."
echo "$foo"
exit 0
bash trap-test.sh
doing something wrong now ..
(!) EXIT HANDLER:
FILE: trap-test.sh
LINE: 6
ERROR CODE: 1
ERROR MESSAGE:
foo: unassigned variable
BACKTRACE IS:
1 main trap-test.sh
Exiting!
function handle_error {
status=$?
last_call=$1
# 127 is 'command not found'
(( status != 127 )) && return
echo "you tried to call $last_call"
return
}
# Trap errors.
trap 'handle_error "$_"' ERR
action () {
# Test if the first parameter is non-zero
# and return straight away if so
if test $1 -ne 0
then
return $1
fi
# Discard the control parameter
# and execute the rest
shift 1
"$@"
local status=$?
# Test the exit status of the command run
# and display an error message on failure
if test ${status} -ne 0
then
echo Command \""$@"\" failed >&2
fi
return ${status}
}
command1 param1 param2 param3...
command2 param1 param2 param3...
command3 param1 param2 param3...
command4 param1 param2 param3...
command5 param1 param2 param3...
command6 param1 param2 param3...
action 0 command1 param1 param2 param3...
action $? command2 param1 param2 param3...
action $? command3 param1 param2 param3...
action $? command4 param1 param2 param3...
action $? command5 param1 param2 param3...
action $? command6 param1 param2 param3...
<<<Error-handling code here>>>