在Bash脚本中引发错误

时间:2015-05-06 13:30:01

标签: linux bash shell error-handling

我想在Bash脚本中引发一条错误消息“Test cases Failed !!!”。如何在Bash中执行此操作?

例如:

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

6 个答案:

答案 0 :(得分:86)

这取决于您希望存储错误消息的位置。

您可以执行以下操作:

echo "Error!" > logfile.log
exit 125

或以下内容:

echo "Error!" 1>&2
exit 64

当您举起异常时,您将停止程序的执行。

您还可以使用exit xxx之类的内容,其中xxx是您可能想要返回操作系统的错误代码(从0到255)。此处12564只是您可以退出的随机代码。当您需要向操作系统指示程序异常停止时(例如发生错误),您需要将非零退出代码传递给exit

作为@chepner pointed out,您可以执行exit 1,这意味着一个未指定的错误

答案 1 :(得分:21)

基本错误处理

如果您的测试用例运行器为失败的测试返回non-zero code,您只需编写:

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这样的错误处理程序的优点是:

  • 我们可以在一个地方标准化所有错误处理逻辑,例如logging,打印stack trace,通知,进行清理等。
  • 通过使错误处理程序获取错误代码作为参数,我们可以使调用者免于出现错误测试退出代码的if块的混乱
  • 如果我们有一个信号处理程序(使用trap),我们可以从那里调用错误处理程序

错误处理和记录库

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

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

相关帖子

答案 2 :(得分:5)

您可以通过多种方式解决此问题。假设您的一个要求是运行包含一些shell命令的shell脚本/函数,并检查脚本是否成功运行并在出现故障时抛出错误。

shell命令通常依赖于返回的退出代码,让shell知道它是否成功或因某些意外事件而失败。

所以你想要做的就是这两个类别

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

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

我应该在我的脚本/函数中使用exit吗?

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

因此,如果您需要对脚本执行清理操作以使脚本终止,则首选来使用exit

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

  

没有!

set -e尝试向shell添加“自动错误检测”。它的目标是在发生错误的任何时候使shell中止,但它会带来许多潜在的陷阱,例如,

  • 属于if测试的命令是免疫的。在该示例中,如果您希望它在对不存在的目录进行test检查时中断,则不会,它会进入其他条件

    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

当{shell}本身以非零错误代码退出时,ERR陷阱不会运行代码,但是当该shell运行的任何命令不属于条件时(如if {{1} }}或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
}

将这些放在第15行包含trap cleanup ERR 的简单脚本上,您将获得的信息为

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上运行shell完成时的清理(例如shell脚本退出)。您还可以同时捕获多个信号。可以在trap.1p - Linux manual page

上找到要捕获的支持信号列表

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

  • EXIT的子shell上无效。 set -e仅限于子shell,永远不会传播到父shell。要在此处执行错误处理,请添加您自己的逻辑以执行false

    (false) || false
  • set -e (false) echo survived 也是如此。由于上述原因,下面的逻辑不起作用。

    trap

答案 3 :(得分:3)

这是一个简单的陷阱,它打印了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)

我经常发现编写一个处理错误消息的函数很有用,所以整个代码更清晰。

# 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时间,截断例如{{1}到12:34:56.%N)。在单词ERROR之后添加冒号和空格,但仅在有提供的错误消息时添加。最后,我们使用先前确定的退出代码退出脚本,正常触发任何陷阱。

一些示例(假设代码位于12:34:56):

script.sh

答案 5 :(得分:1)

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

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

    ./runTests &> output.log
    

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

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

    1. 在脚本中引入日志文件
    2. 在您的脚本中通过硬编码添加日志文件:

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