220 error-handling bash error-logging
你最喜欢在Bash中处理错误的方法是什么?我在网上找到的处理错误的最好例子是由William Shotts,Jr在http://www.linuxcommand.org撰写.
他建议在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
# 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."
Run Code Online (Sandbox Code Playgroud)
您是否有更好的错误处理例程,您在Bash脚本中使用?
Cha*_*ffy 150
使用陷阱!
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
Run Code Online (Sandbox Code Playgroud)
...然后,每当您创建临时文件时:
temp_foo="$(mktemp -t foobar.XXXXXX)"
tempfiles+=( "$temp_foo" )
Run Code Online (Sandbox Code Playgroud)
并将$temp_foo
在退出时删除,并打印当前行号.(set -e
同样会给你出错的行为,虽然它带有严重的警告并削弱了代码的可预测性和可移植性).
您可以让陷阱error
为您调用(在这种情况下,它使用默认退出代码1并且没有消息)或自己调用它并提供显式值; 例如:
error ${LINENO} "the foobar failed" 2
Run Code Online (Sandbox Code Playgroud)
将以状态2退出,并给出明确的消息.
Bru*_*ine 116
这是一个很好的解决方案.我只是想补充一下
set -e
Run Code Online (Sandbox Code Playgroud)
作为一种基本的错误机制.如果简单命令失败,它将立即停止您的脚本.我认为这应该是默认行为:因为这样的错误几乎总是表示意外的事情,所以继续执行以下命令并不是真的"理智".
Luc*_*one 74
阅读本页面上的所有答案都给了我很多启发.
所以,这是我的提示:
文件内容:lib.trap.sh
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
Run Code Online (Sandbox Code Playgroud)
用法示例:
文件内容:trap-test.sh
#!/bin/bash
source 'lib.trap.sh'
echo "doing something wrong now .."
echo "$foo"
exit 0
Run Code Online (Sandbox Code Playgroud)
运行:
bash trap-test.sh
Run Code Online (Sandbox Code Playgroud)
输出:
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!
Run Code Online (Sandbox Code Playgroud)
从下面的屏幕截图中可以看出,输出是彩色的,错误消息以使用的语言显示.
小智 22
"set -e"的等效替代方法是
set -o errexit
Run Code Online (Sandbox Code Playgroud)
它使旗帜的含义比"-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
Run Code Online (Sandbox Code Playgroud)
这排除了其他响应中提到的正确错误处理,但是快速有效(就像bash一样).
nii*_*ani 19
受到这里提出的想法的启发,我开发了一种可读且方便的方法来处理我的bash样板项目中的bash脚本中的错误.
通过简单地获取库,您可以获得以下开箱即用(即它将停止执行任何错误,就好像使用set -e
感谢trap
on ERR
和一些bash-fu):
有一些额外的功能可以帮助处理错误,例如try和catch,或者throw关键字,它允许你在一个点上中断执行以查看回溯.此外,如果终端支持它,它会吐出电源线表情符号,为输出的颜色部分着色以提高可读性,并强调在代码行的上下文中导致异常的方法.
缺点是 - 它不可移植 - 代码在bash中工作,可能只有> = 4(但我想它可以通过一些努力来移植bash 3).
代码被分成多个文件以便更好地处理,但我从Luca Borrione的上述答案中得到了回溯的启发.
要阅读更多信息或查看源代码,请参阅GitHub:
https://github.com/niieani/bash-oo-framework#error-handling-with-exceptions-and-throw
小智 11
我更喜欢一些很容易打电话的东西.所以我使用的东西看起来有点复杂,但很容易使用.我通常只是将下面的代码复制并粘贴到我的脚本中.代码后面有一个解释.
#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 ))`):"'
Run Code Online (Sandbox Code Playgroud)
我通常在error_exit函数的一侧调用清理函数,但这在脚本之间有所不同,所以我把它遗漏了.陷阱捕获常见的终止信号并确保一切都得到清理.别名是真正的魔力.我想检查一切是否失败.所以一般来说我用"if!"调用程序.类型声明.通过从行号中减去1,别名将告诉我故障发生的位置.打电话也很简单,而且非常白痴.下面是一个示例(只需将/ bin/false替换为您要调用的任何内容).
#This is an example useage, it will print out
#Error prog-name (@1): Who knew false is false.
if ! /bin/false ; then
die "Who knew false is false."
fi
Run Code Online (Sandbox Code Playgroud)
另一个考虑因素是返回的退出代码.只是" 1
"是非常标准的,虽然有一些保留退出代码,bash本身使用,并且同一页面认为用户定义的代码应该在64-113范围内,以符合C/C++标准.
您还可以考虑mount
用于其退出代码的位向量方法:
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
Run Code Online (Sandbox Code Playgroud)
OR
- 将代码放在一起可让您的脚本发出多个同时发生的错误信号.
我使用以下陷阱代码,它还允许通过管道和“时间”命令跟踪错误
#!/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
Run Code Online (Sandbox Code Playgroud)
我用过
die() {
echo $1
kill $$
}
Run Code Online (Sandbox Code Playgroud)
前; 我想是因为“退出”由于某种原因对我来说失败了。不过,上述默认值似乎是个好主意。