【问题标题】:Raise error in a Bash script在 Bash 脚本中引发错误
【发布时间】:2015-07-16 16:20:41
【问题描述】:

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

例如:

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

【问题讨论】:

标签: linux bash shell error-handling scripting


【解决方案1】:

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

您可以执行以下操作:

echo "Error!" > logfile.log
exit 125

或以下:

echo "Error!" 1>&2
exit 64

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

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

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

【讨论】:

    【解决方案2】:

    基本错误处理

    如果您的测试用例运行器为失败的测试返回 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


    相关帖子

    【讨论】:

      【解决方案3】:

      还有更多方法可以解决这个问题。假设您的要求之一是运行包含一些 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 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
      

      将这些放在一个简单的脚本中,该脚本在第 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 还提供了选项,无论错误如何,都可以在 shell 完成时(例如,你的 shell 脚本退出),在信号 EXIT 上运行清理。您还可以同时捕获多个信号。可以在trap.1p - Linux manual page 上找到要捕获的受支持信号列表

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

      • 在带有set -e 的子shell 上不起作用。 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(这会触发陷阱)。

        对于详细的功能,让陷阱调用处理函数。如果您需要进行更多清理等,您始终可以在您的 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】:

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

          【讨论】:

            【解决方案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.%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"
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-09-08
              • 2011-02-14
              • 1970-01-01
              • 2014-12-09
              • 2011-06-30
              • 2015-08-10
              • 2011-10-14
              相关资源
              最近更新 更多