在Bash脚本中引发错误


103

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

例如:

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

1
您想对这个错误发生什么?您的脚本如何命名?是一个脚本还是多个脚本?脚本的用途是什么?
Etan Reisner,2015年

只是一个脚本。我使用ubuntu终端程序(如./script/test.sh)将其称为
Naveen Kumar,



没有爱echo you screwed up at ... | mail -s BUG $bugtrackeremailaddress吗?
infixed

Answers:


121

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

您可以执行以下操作:

echo "Error!" > logfile.log
exit 125

或以下内容:

echo "Error!" 1>&2
exit 64

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

您还可以使用类似“ exit xxx哪里xxx是您可能要返回到操作系统的错误代码(从0到255)”之类的东西。在这里12564只是可以退出的随机代码。当您需要向OS指示程序异常停止(例如,发生错误)时,您需要将非零退出代码传递给exit

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


12
或者您可以将其发送到应该出错的stderr。

如何将其发送到stderr?
纳文·库玛

2
@ user3078630,我刚刚编辑了我的答案。1>&2将达到目的
ForceBru 2015年

如果出现错误,您也应该以非零的退出状态退出。exit本身采用了最近完成的命令的退出状态,这可能是0
chepner

3
除非您有特定的含义,否则应仅使用exit 1,按照惯例,这表示未指定的错误。
chepner

36

基本错误处理

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

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是:

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

错误处理和日志记录库

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

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


相关文章


9

您可以通过多种方法来解决此问题。假设您的要求之一是运行包含一些Shell命令的Shell脚本/函数,并检查脚本是否成功运行,并在失败的情况下抛出错误。

shell命令通常依赖于返回的退出代码,以使外壳知道由于某些意外事件而成功还是失败。

所以您想做的事就属于这两类

  • 错误退出
  • 退出并清除错误

根据您要执行的操作,可以使用一些shell选项。对于第一种情况,外壳程序提供了一个选项,set -e对于第二种情况,您可以trapEXIT

我应该exit在脚本/功能中使用吗?

exit通常,使用它可以提高可读性。在某些例程中,一旦知道答案,就想立即退出到调用例程。如果例程以这样的方式定义,即一旦检测到错误就不需要进一步清理,则不立即退出意味着您必须编写更多代码。

因此,如果您需要对脚本执行清理操作以使脚本的终止清理干净,则最好不要使用exit

我应该set -e在退出时使用错误提示吗?

没有!

set -e试图向外壳添加“自动错误检测”。它的目标是在发生错误时使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,请trapERR伪信号上使用a 。

ERR陷阱没有运行代码当壳本身具有非零的错误代码退出,但是当由壳这不是一个条件的一部分(如在如果任何命令运行cmd,或者cmd ||)出口具有非零退出状态。

通常的做法是,我们定义一个陷阱处理程序,以提供有关哪些行以及导致退出的原因的其他调试信息。请记住,导致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

将其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脚本退出)的清理,对信号EXIT。您也可以同时捕获多个信号。可以在trap.1p上找到要捕获的受支持信号的列表-Linux手册页

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

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

    set -e
    (false)
    echo survived
  • 同样trap也会发生。由于上述原因,以下逻辑无法正常工作。

    trap 'echo error' ERR
    (false)

5

这是一个简单的陷阱,可打印出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并非如此(因此是糖),并且其他绊倒陷阱的事情可能看起来有些愚蠢。我还是喜欢这种方法。


4

您有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中更好的选择。另外,如果您创建了一个包含多个组件的大型脚本,则可能希望以不同的方式记录每个部分,而第二种方法是唯一的方法选项。


3

我经常发现编写用于处理错误消息的函数很有用,因此代码总体上更清晰。

# 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"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.