在 Bash 脚本中引发错误
- 2024-10-11 08:36:00
- admin 原创
- 80
问题描述:
我想在 Bash 脚本中引发错误信息“测试用例失败!!!”。如何在 Bash 中执行此操作?
例如:
if [ condition ]; then
raise error "Test cases failed !!!"
fi
解决方案 1:
这取决于您希望将错误消息存储在哪里。
您可以执行以下操作:
echo "Error!" > logfile.log
exit 125
或者以下内容:
echo "Error!" 1>&2
exit 64
当你引发异常时,程序的执行就会停止。
您还可以使用类似您可能希望返回操作系统的错误代码在exit xxx
哪里(从 0 到 255)之类的东西。这里和只是您可以退出的随机代码。当您需要向操作系统指示程序异常停止(例如发生错误)时,您需要将非零退出代码传递给。xxx
`12564
exit`
正如@chepner指出的那样,您可以这样做exit 1
,这意味着未指定的错误。
解决方案 2:
基本错误处理
如果您的测试用例运行器对失败的测试返回非零代码,您可以简单地写道:
test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
printf '%s
' "Test case x failed" >&2 # write error message to stderr
exit 1 # or exit $test_result
fi
或者更短一点:
if ! test_handler test_case_x; then
printf '%s
' "Test case x failed" >&2
exit 1
fi
或者最短的:
test_handler test_case_x || { printf '%s
' "Test case x failed" >&2; exit 1; }
要使用 test_handler 的退出代码退出:
test_handler test_case_x || { ec=$?; printf '%s
' "Test case x failed" >&2; exit $ec; }
高级错误处理
如果您想采取更全面的方法,您可以使用错误处理程序:
exit_if_error() {
local exit_code=$1
shift
[[ $exit_code ]] && # do nothing if no error code passed
((exit_code != 0)) && { # do nothing if error code is 0
printf 'ERROR: %s
' "$@" >&2 # we can use better logging here
exit "$exit_code" # we could also check to make sure
# error code is numeric when passed
}
}
然后在运行测试用例后调用它:
run_test_case test_case_x
exit_if_error $? "Test case x failed"
或者
run_test_case test_case_x || exit_if_error $? "Test case x failed"
拥有如下错误处理程序的优点exit_if_error
是:
我们可以在一个地方标准化所有错误处理逻辑,例如日志记录、打印堆栈跟踪、通知、清理等
通过让错误处理程序获取错误代码作为参数,我们可以让调用者免于
if
测试退出代码是否存在错误的混乱块如果我们有一个信号处理程序(使用trap),我们可以从那里调用错误处理程序
错误处理和日志记录库
以下是错误处理和日志记录的完整实现:
https://github.com/codeforester/base/blob/master/lib/stdlib.sh
相关文章
Bash 中的错误处理
Bash Hackers Wiki 上的'caller' 内置命令
Linux 中有任何标准退出状态代码吗?
BashFAQ/105 - 为什么 set -e(或 set -o errexit 或 trap ERR)没有按照我预期的方式运行?
Bash 中的
__FILE__
, 等效项__LINE__
Bash 中是否有 TRY CATCH 命令
要向错误处理程序添加堆栈跟踪,您可能需要查看此帖子:Bash 脚本调用的已执行程序的跟踪
忽略 shell 脚本中的特定错误
在 shell 管道中捕获错误代码
如何管理 shell 脚本中的日志详细程度?
如何在 Bash 中记录函数名称和行号?
在 Bash 中双方括号 [[ ]] 比单方括号 [ ] 更好吗?
解决方案 3:
还有几种方法可以解决这个问题。假设您的一个需求是运行一个包含一些 shell 命令的 shell 脚本/函数,并检查该脚本是否成功运行,并在失败时抛出错误。
shell 命令通常依赖返回的退出代码来让 shell 知道它是成功还是由于某些意外事件而失败。
所以你想做的事情属于这两类
出错时退出
出现错误时退出并清理
根据您要执行的操作,可以使用 shell 选项。对于第一种情况,shell 提供了一个选项set -e
,对于第二种情况,您可以执行trap
onEXIT
我应该exit
在我的脚本/函数中使用吗?
使用exit
通常可以提高可读性在某些例程中,一旦您知道答案,您就会希望立即退出到调用例程。如果例程的定义方式是一旦检测到错误就不需要进一步清理,那么不立即退出意味着您必须编写更多代码。
因此,如果您需要对脚本执行清理操作以使脚本干净地终止,最好不要使用exit
。
退出时我应该使用set -e
错误吗?
不!
set -e
是尝试在 shell 中添加“自动错误检测”。其目标是让 shell 在发生错误时中止,但它带来了许多潜在的陷阱,例如,
作为 if 测试一部分的命令不受此限制。在示例中,如果您希望它
test
在检查不存在的目录时中断,它不会中断,而是进入 else 条件
set -e
f() { test -d nosuchdir && echo no dir; }
f
echo survived
管道中除最后一个命令之外的命令均不受此影响。在下面的示例中,因为最近执行的(最右边的)命令的退出代码被视为(
cat
)并且它是成功的。这可以通过设置选项来避免set -o pipefail
,但这仍然是一个警告。
set -e
somecommand that fails | cat -
echo survived
建议使用 -trap
退出时
结论是,如果您希望能够处理错误而不是盲目退出,那么请不要使用,而是在伪信号上set -e
使用。trap
`ERR`
陷阱ERR
不是在 shell 本身以非零错误代码退出时运行代码,而是当该 shell 运行的任何不属于条件的命令(例如 ifcmd
或cmd ||
)以非零退出状态退出时运行代码。
通常的做法是定义一个陷阱处理程序,以提供有关哪一行以及什么原因导致退出的额外调试信息。请记住,导致ERR
信号的最后一个命令的退出代码此时仍然可用。
cleanup() {
exitcode=$?
printf 'error condition hit
' 1>&2
printf 'exit code returned: %s
' "$exitcode"
printf 'the command executing at the time of the error was: %s
' "$BASH_COMMAND"
printf 'command present on line: %d' "${BASH_LINENO[0]}"
# Some more clean up code can be added here before exiting
exit $exitcode
}
我们只需在失败的脚本上使用下面的处理程序即可
trap cleanup ERR
把这些放在一个简单的脚本中,该脚本包含false
第 15 行,您将获得以下信息
error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15
还trap
提供了与错误无关的选项,只需在 shell 完成时(例如,您的 shell 脚本退出)在信号时运行清理即可。您还可以同时捕获多个信号。可以在trap.1p - Linux 手册页EXIT
上找到支持捕获的信号列表
要注意的另一件事是,如果您正在处理涉及子 shell 的情况,那么提供的所有方法都不起作用,在这种情况下,您可能需要添加自己的错误处理。
在子 shell 上,使用
set -e
不起作用。false
仅限于子 shell,永远不会传播到父 shell。要在此处执行错误处理,请添加您自己的逻辑来执行(false) || false
set -e
(false)
echo survived
同样的情况
trap
也发生在。由于上述原因,下面的逻辑不起作用。
trap 'echo error' ERR
(false)
解决方案 4:
这是一个简单的陷阱,它会将失败的最后一个参数打印到 STDERR,报告失败的行,并以行号作为退出代码退出脚本。请注意,这些想法并不总是很好,但这展示了一些您可以构建的创造性应用程序。
trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
我将其放入一个带有循环的脚本中以进行测试。我只是检查一些随机数是否命中;您可以使用实际测试。如果我需要保释,我会使用我想要抛出的消息调用 false(这会触发陷阱)。
对于复杂的功能,让 trap 调用处理函数。如果您需要进行更多清理等,您始终可以在 arg ($_) 上使用 case 语句。分配给 var 以获得一些语法糖 -
trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false
while :
do x=$(( $RANDOM % 10 ))
case "$x" in
0) $throw "DIVISION BY ZERO" ;;
3) $raise "MAGIC NUMBER" ;;
*) echo got $x ;;
esac
done
示例输出:
# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6
显然,你可以
runTest1 "Test1 fails" # message not used if it succeeds
设计改进空间很大。
缺点包括false
不太美观(因此需要糖),而且其他东西掉入陷阱可能看起来有点愚蠢。不过,我喜欢这种方法。
解决方案 5:
您有两个选择:将脚本的输出重定向到文件,在脚本中引入日志文件和
将输出重定向到文件:
这里假设脚本输出所有必要的信息,包括警告和错误消息。然后,您可以将输出重定向到您选择的文件。
./runTests &> output.log
上述命令将标准输出和错误输出重定向到您的日志文件。
使用这种方法您不必在脚本中引入日志文件,因此逻辑会更简单一些。
向脚本中引入日志文件:
在您的脚本中通过硬编码添加日志文件:
logFile='./path/to/log/file.log'
或者通过参数传递:
logFile="${1}" # This assumes the first parameter to the script is the log file
最好将执行时的时间戳添加到脚本顶部的日志文件中:
date '+%Y%-m%d-%H%M%S' >> "${logFile}"
然后您可以将错误消息重定向到日志文件
if [ condition ]; then
echo "Test cases failed!!" >> "${logFile}";
fi
这会将错误附加到日志文件并继续执行。如果您想在发生严重错误时停止执行,可以使用以下exit
脚本:
if [ condition ]; then
echo "Test cases failed!!" >> "${logFile}";
# Clean up if needed
exit 1;
fi
请注意,exit 1
表示程序由于未指定的错误而停止执行。您可以根据需要自定义它。
使用此方法,您可以自定义日志并为脚本的每个组件提供不同的日志文件。
如果您有一个相对较小的脚本或者想要执行别人的脚本而不对其进行修改,则第一种方法更为合适。
如果您始终希望日志文件位于同一位置,那么这是两种方法中更好的选择。此外,如果您创建了一个包含多个组件的大脚本,那么您可能希望以不同的方式记录每个部分,第二种方法是您唯一的选择。
解决方案 6:
我经常发现编写一个函数来处理错误消息很有用,这样代码整体上会更干净。
# Usage: die [exit_code] [error message]
die() {
local code=$? now=$(date +%T.%N)
if [ "$1" -ge 0 ] 2>/dev/null; then # assume $1 is an error code if numeric
code="$1"
shift
fi
echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
exit $code
}
这将从上一个命令中获取错误代码,并将其用作退出整个脚本时的默认错误代码。它还会记录时间,如果支持,则以微秒为单位(GNU 日期%N
以纳秒为单位,稍后我们会将其截断为微秒)。
如果第一个选项为零或正整数,则它将成为退出代码,我们将其从选项列表中删除。然后,我们将消息报告给标准错误,其中包含脚本名称、单词“ERROR”和时间(我们使用参数扩展将纳秒截断为微秒,或者对于非 GNU 时间,截断为12:34:56.%N
)12:34:56
。在单词 ERROR 后面添加冒号和空格,但仅在提供错误消息时才添加。最后,我们使用之前确定的退出代码退出脚本,正常触发任何陷阱。
一些例子(假设代码位于script.sh
):
if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"
$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"
$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件