Bash 中的错误处理
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/64786/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Error handling in Bash
提问by
What is your favorite method to handle errors in Bash? The best example of handling errors I have found on the web was written by William Shotts, Jr at http://www.linuxcommand.org.
你最喜欢用什么方法来处理 Bash 中的错误?我在网络上发现的处理错误的最佳示例是 William Shotts, Jr 在http://www.linuxcommand.org 上编写的。
He suggests using the following function for error handling in Bash:
他建议在 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 (tempfiles=( )
cleanup() {
rm -f "${tempfiles[@]}"
}
trap cleanup 0
error() {
local parent_lineno=""
local message=""
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
).
# Reference: This was copied from <http://www.linuxcommand.org/wss0150.php>
PROGNAME=$(basename temp_foo="$(mktemp -t foobar.XXXXXX)"
tempfiles+=( "$temp_foo" )
)
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."
Do you have a better error handling routine that you use in Bash scripts?
你有在 Bash 脚本中使用的更好的错误处理例程吗?
回答by Charles Duffy
Use a trap!
使用陷阱!
error ${LINENO} "the foobar failed" 2
...then, whenever you create a temporary file:
...然后,每当您创建临时文件时:
set -e
and $temp_foo
will be deleted on exit, and the current line number will be printed. (set -e
will likewise give you exit-on-error behavior, though it comes with serious caveatsand weakens code's predictability and portability).
和$temp_foo
将在退出时删除,并打印当前行号。(set -e
同样会为您提供错误退出行为,尽管它带有严重的警告并削弱了代码的可预测性和可移植性)。
You can either let the trap call error
for you (in which case it uses the default exit code of 1 and no message) or call it yourself and provide explicit values; for instance:
您可以让陷阱error
为您调用(在这种情况下,它使用默认退出代码 1 并且没有消息)或自己调用它并提供显式值;例如:
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_=""
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
will exit with status 2, and give an explicit message.
将以状态 2 退出,并给出明确的消息。
回答by Bruno De Fraine
That's a fine solution. I just wanted to add
这是一个很好的解决方案。我只是想补充
#!/bin/bash
source 'lib.trap.sh'
echo "doing something wrong now .."
echo "$foo"
exit 0
as a rudimentary error mechanism. It will immediately stop your script if a simple command fails. I think this should have been the default behavior: since such errors almost always signify something unexpected, it is not really 'sane' to keep executing the following commands.
作为一种基本的错误机制。如果一个简单的命令失败,它会立即停止您的脚本。我认为这应该是默认行为:由于此类错误几乎总是表示出乎意料的事情,因此继续执行以下命令并不是真正的“理智”。
回答by Luca Borrione
Reading all the answers on this page inspired me a lot.
So, here's my hint:
file content: lib.trap.sh
阅读此页面上的所有答案对我启发很大。
所以,这是我的提示:
文件内容:lib.trap.sh
bash trap-test.sh
Example of usage:
file content: trap-test.sh
用法示例:
文件内容: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!
Running:
跑步:
set -o errexit
Output:
输出:
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
As you can see from the screenshot below, the output is colored and the error message comes in the used language.
正如您从下面的屏幕截图中看到的,输出是彩色的,错误消息是使用的语言。
回答by Ben Scholbrock
An equivalent alternative to "set -e" is
“set -e”的等效替代方法是
#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 #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
(@`echo $(( $LINENO - 1 ))`):"'
It makes the meaning of the flag somewhat clearer than just "-e".
它使标志的含义比“-e”更清楚一些。
Random addition: to temporarily disable the flag, and return to the default (of continuing execution regardless of exit codes), just use
随机添加:暂时禁用标志,并返回默认值(无论退出代码如何继续执行),只需使用
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
This precludes proper error handling mentioned in other responses, but is quick & effective (just like bash).
这排除了其他响应中提到的正确错误处理,但快速有效(就像 bash 一样)。
回答by niieani
Inspired by the ideas presented here, I have developed a readable and convenient way to handle errors in bash scripts in my bash boilerplate project.
受到这里提出的想法的启发,我开发了一种可读且方便的方法来处理我的bash 样板项目中 bash 脚本中的错误。
By simply sourcing the library, you get the following out of the box (i.e. it will halt execution on any error, as if using set -e
thanks to a trap
on ERR
and some bash-fu):
通过简单地获取库,您可以立即获得以下内容(即它会在出现任何错误时停止执行,就像使用set -e
a trap
onERR
和一些bash-fu 一样):
There are some extra features that help handle errors, such as try and catch, or the throwkeyword, that allows you to break execution at a point to see the backtrace. Plus, if the terminal supports it, it spits out powerline emojis, colors parts of the output for great readability, and underlines the method that caused the exception in the context of the line of code.
有一些额外的功能可以帮助处理错误,例如try 和 catch或throw关键字,它们允许您在某个点中断执行以查看回溯。另外,如果终端支持它,它会吐出电力线表情符号,为输出的部分着色以提高可读性,并在代码行的上下文中强调导致异常的方法。
The downside is - it's not portable - the code works in bash, probably >= 4 only (but I'd imagine it could be ported with some effort to bash 3).
缺点是 - 它不可移植 - 代码在 bash 中工作,可能仅 >= 4(但我想它可以通过一些努力移植到 bash 3)。
The code is separated into multiple files for better handling, but I was inspired by the backtrace idea from the answer above by Luca Borrione.
代码被分成多个文件以便更好地处理,但我的灵感来自Luca Borrione 上面答案中的回溯想法。
To read more or take a look at the source, see GitHub:
要阅读更多信息或查看源代码,请参阅 GitHub:
https://github.com/niieani/bash-oo-framework#error-handling-with-exceptions-and-throw
https://github.com/niieani/bash-oo-framework#error-handling-with-exceptions-and-throw
回答by Michael Nooner
I prefer something really easy to call. So I use something that looks a little complicated, but is easy to use. I usually just copy-and-paste the code below into my scripts. An explanation follows the code.
我更喜欢真正容易调用的东西。所以我使用了一些看起来有点复杂但很容易使用的东西。我通常只是将下面的代码复制并粘贴到我的脚本中。代码后面有解释。
#!/bin/bash
set -o pipefail # trace ERR through pipes
set -o errtrace # trace ERR through 'time command' and other functions
function error() {
JOB="#!/bin/bash
error_exit()
{
if [ "$?" != "0" ]; then
log.sh ""
exit 1
fi
}
" # job name
LASTLINE="" # line of error occurrence
LASTERR="" # error code
echo "ERROR in ${JOB} : line ${LASTLINE} with exit code ${LASTERR}"
exit 1
}
trap 'error ${LINENO} ${?}' ERR
I usually put a call to the cleanup function in side the error_exit function, but this varies from script to script so I left it out. The traps catch the common terminating signals and make sure everything gets cleaned up. The alias is what does the real magic. I like to check everything for failure. So in general I call programs in an "if !" type statement. By subtracting 1 from the line number the alias will tell me where the failure occurred. It is also dead simple to call, and pretty much idiot proof. Below is an example (just replace /bin/false with whatever you are going to call).
我通常在 error_exit 函数旁边调用 cleanup 函数,但是这因脚本而异,所以我把它省略了。陷阱捕捉常见的终止信号并确保一切都得到清理。别名才是真正的魔法。我喜欢检查一切是否失败。所以总的来说,我在“如果!”中调用程序。类型声明。通过从行号中减去 1,别名将告诉我失败发生的位置。调用它也非常简单,而且几乎是白痴的证明。下面是一个示例(只需将 /bin/false 替换为您要调用的任何内容)。
#!/bin/bash
cd /home/myuser/afolder
error_exit "Unable to switch to folder"
rm *
error_exit "Unable to delete all files"
回答by yukondude
Another consideration is the exit code to return. Just "1
" is pretty standard, although there are a handful of reserved exit codes that bash itself uses, and that same page argues that user-defined codes should be in the range 64-113 to conform to C/C++ standards.
另一个考虑因素是要返回的退出代码。只是“ 1
”是非常标准的,尽管有一些bash 本身使用的保留退出代码,并且同一页面认为用户定义的代码应该在 64-113 的范围内以符合 C/C++ 标准。
You might also consider the bit vector approach that mount
uses for its exit codes:
您还可以考虑mount
用于其退出代码的位向量方法:
die() {
echo
kill $$
}
OR
-ing the codes together allows your script to signal multiple simultaneous errors.
OR
- 将代码放在一起允许您的脚本发出多个同时发生的错误信号。
回答by Olivier Delrieu
I use the following trap code, it also allows errors to be traced through pipes and 'time' commands
我使用以下陷阱代码,它还允许通过管道和“时间”命令跟踪错误
##代码##回答by Nelson Rodriguez
Not sure if this will be helpful to you, but I modified some of the suggested functions here in order to include the check for the error (exit code from prior command) within it. On each "check" I also pass as a parameter the "message" of what the error is for logging purposes.
不确定这是否对您有帮助,但我在这里修改了一些建议的函数,以便在其中包含对错误的检查(先前命令的退出代码)。在每个“检查”中,我还将错误的“消息”作为参数传递以用于记录目的。
##代码##Now to call it within the same script (or in another one if I use export -f error_exit
) I simply write the name of the function and pass a message as parameter, like this:
现在要在同一个脚本中调用它(或者在另一个脚本中,如果我使用export -f error_exit
),我只需编写函数的名称并将消息作为参数传递,如下所示:
Using this I was able to create a really robust bash file for some automated process and it will stop in case of errors and notify me (log.sh
will do that)
使用它,我能够为一些自动化过程创建一个非常强大的 bash 文件,它会在出现错误时停止并通知我(log.sh
会这样做)
回答by pjz
I've used
我用过
##代码##before; i think because 'exit' was failing for me for some reason. The above defaults seem like a good idea, though.
前; 我想是因为“退出”出于某种原因对我来说失败了。不过,上述默认值似乎是个好主意。