繁体   English   中英

在 Bash 脚本中引发错误

[英]Raise error in a Bash script

我想在 Bash 脚本中引发一个错误消息“测试用例失败!!!”。 如何在 Bash 中做到这一点?

例如:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi

这取决于您希望将错误消息存储在何处。

您可以执行以下操作:

echo "Error!" > logfile.log
exit 125

或以下内容:

echo "Error!" 1>&2
exit 64

当您引发异常时,您将停止程序的执行。

您还可以使用类似exit xxx ,其中xxx是您可能想要返回到操作系统的错误代码(从 0 到 255)。 这里12564只是您可以退出的随机代码。 当您需要向操作系统表明程序异常停止(例如发生错误)时,您需要传递一个非零退出代码exit

正如@chepner 指出的那样,您可以执行exit 1 ,这将意味着未指定的错误

基本错误处理

如果您的测试用例运行程序为失败的测试返回非零代码,您可以简单地编写:

test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
  printf '%s\n' "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\n' "Test case x failed" >&2
  exit 1
fi

或者最短的:

test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }

使用 test_handler 的退出代码退出:

test_handler test_case_x || { ec=$?; printf '%s\n' "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\n' "$@" >&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这样的错误处理程序的exit_if_error是:

  • 我们可以在一个地方标准化所有错误处理逻辑,例如日志记录、打印堆栈跟踪、通知、清理等
  • 通过让错误处理程序获取错误代码作为参数,我们可以使调用者免于测试错误退出代码的if块的混乱
  • 如果我们有一个信号处理程序(使用trap ),我们可以从那里调用错误处理程序

错误处理和日志库

这是错误处理和日志记录的完整实现:

https://github.com/codeforester/base/blob/master/lib/stdlib.sh


相关文章

还有更多的方法可以解决这个问题。 假设您的要求之一是运行包含几个 shell 命令的 shell 脚本/函数,并检查脚本是否成功运行并在失败时抛出错误。

shell 命令通常依靠返回的退出代码来让 shell 知道它是成功还是由于某些意外事件而失败。

所以你想做的属于这两类

  • 出错退出
  • 出错时退出和清理

根据您要执行的操作,可以使用 shell 选项。 对于第一种情况,shell 提供了一个带有set -e的选项,对于第二种情况,您可以在EXIT上做一个trap

我应该在脚本/函数中使用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 ,请在ERR伪信号上使用trap

ERR陷阱不是在 shell 本身以非零错误代码退出时运行代码,而是当该 shell 运行的任何不属于条件的命令(如 if cmdcmd || )以非零退出状态。

一般的做法是我们定义一个陷阱处理程序来提供关于哪一行以及导致退出的原因的附加调试信息。 请记住,此时导致ERR信号的最后一个命令的退出代码仍然可用。

cleanup() {
    exitcode=$?
    printf 'error condition hit\n' 1>&2
    printf 'exit code returned: %s\n' "$exitcode"
    printf 'the command executing at the time of the error was: %s\n' "$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

将这些放在一个简单的脚本中,其中第 15 行包含false ,您将获得的信息为

error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15

trap还提供选项,而不管错误如何,只在外壳完成时(例如,您的外壳脚本退出),在信号EXIT上运行清理。 您还可以同时捕获多个信号。 可以在trap.1p - Linux 手册页上找到要捕获的受支持信号列表

要注意的另一件事是要了解,如果您正在处理涉及子 shell 的情况,则所提供的方法都不起作用,在这种情况下,您可能需要添加自己的错误处理。

  • 在带有set -e的子外壳上不起作用。 false仅限于子 shell,永远不会传播到父 shell。 要在此处进行错误处理,请添加您自己的逻辑 do (false) || false (false) || false

     set -e (false) echo survived
  • trap也会发生同样的情况。 由于上述原因,下面的逻辑将不起作用。

     trap 'echo error' ERR (false)

这是一个简单的陷阱,它打印任何失败的 STDERR 的最后一个参数,报告它失败的行,并以行号作为退出代码退出脚本。 请注意,这些并不总是很好的想法,但这展示了一些您可以构建的创造性应用程序。

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR

我把它放在一个带有循环的脚本中来测试它。 我只是检查一些随机数的命中; 您可能会使用实际测试。 如果我需要保释,我会用我想抛出的消息调用 false(触发陷阱)。

对于详细的功能,让陷阱调用一个处理函数。 如果您需要进行更多清理等,您始终可以在 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不漂亮(因此是糖),并且其他绊倒陷阱的事情可能看起来有点愚蠢。 不过,我喜欢这种方法。

您有 2 个选项:将脚本的输出重定向到一个文件,在脚本中引入一个日志文件和

  1. 将输出重定向到文件

这里假设脚本输出所有必要的信息,包括警告和错误消息。 然后,您可以将输出重定向到您选择的文件。

./runTests &> output.log

上面的命令将标准输出和错误输出都重定向到您的日志文件。

使用这种方法,您不必在脚本中引入日志文件,因此逻辑要简单一些。

  1. 将日志文件引入脚本

在您的脚本中,通过硬编码添加一个日志文件:

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表示程序由于未指定的错误而停止执行。 如果您愿意,您可以自定义它。

使用这种方法,您可以自定义日志并为脚本的每个组件使用不同的日志文件。


如果你有一个比较小的脚本或者想执行别人的脚本而不修改它,第一种方法更合适。

如果您总是希望日志文件位于同一位置,这是 2 中更好的选择。此外,如果您创建了一个包含多个组件的大脚本,那么您可能希望以不同的方式记录每个部分,第二种方法是您唯一的方法选项。

我经常发现编写一个函数来处理错误消息很有用,这样代码整体上更清晰。

# 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.%N12: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"

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM