Bash 中是否有 TRY CATCH 命令

Is there a TRY CATCH command in Bash

提问人:Lee Probert 提问时间:2/25/2014 最后编辑:codeforesterLee Probert 更新时间:11/21/2023 访问量:629960

问:

我正在编写 shell 脚本,需要检查是否已安装终端应用程序。我想使用 TRY/CATCH 命令来执行此操作,除非有更简洁的方法。

bash shell 错误处理

评论

0赞 devnull 2/25/2014
也就是说,说出来似乎可以帮助您找到解决问题的方法。help test
3赞 Ben 10/1/2014
try/catch/finally 块不是一个命令,而是一个构造
0赞 blong 7/24/2015
Linux shell 的可能副本 try catch finally
1赞 user1934428 2/18/2020
@LeeProbert : 由于你在 bash 中没有异常,我想知道你想抓住什么。最接近异常方向的是信号,您可以使用该命令捕获大多数(不是全部)信号。trap

答:

933赞 Jayesh Bhoi 2/25/2014 #1

Bash 中有 TRY CATCH 命令吗?

不。

Bash 没有像许多编程语言那样多的奢侈品。

bash 中没有;但是,可以使用 或 实现类似的行为。try/catch&&||

用:||

如果失败,则按如下方式运行command1command2

command1 || command2

同样,如果成功,则使用 将运行&&command2command1

最接近的近似值如下try/catch

{ # try

    command1 &&
    #save your output

} || { # catch
    # save log for exception 
}

此外,bash 还包含一些错误处理机制

set -e

如果任何简单命令失败,它将停止脚本。

还有为什么不.它是你最好的朋友。if...else

评论

27赞 chepner 2/25/2014
有了这个,你需要注意代码不会失败,否则“catch”块仍然会执行。#save your output
10赞 Luke Griffiths 3/12/2016
有人建议使用构造。这是否意味着 bash 命令如果运行成功,则解析为“真实”,如果失败,则解析为“虚假”?if...else
7赞 Luke Davis 9/14/2017
对于这个帖子的读者:似乎这不一定是最好的做事方式;以下是一些反驳/特殊情况: mywiki.wooledge.org/BashFAQ/105set -e
4赞 Jayesh Bhoi 9/4/2020
@alper答案中的脚本是一个示例,您必须根据您使用的命令类型进行修改。前任。#!/bin/bash { echo "This is try" && } || { echo "This is catch" }
5赞 Brian 11/10/2020
我的代码在删除后可以正常工作。 用于连接块中的两个命令。它不应该添加到块的末尾。&&&&trytry
38赞 Alfe 2/25/2014 #2

bash在检测到错误状态时不会中止正在运行的执行(除非您设置了标志)。提供这种服务的编程语言这样做是为了抑制由于这种特殊情况而导致的“救助”(因此通常称为“异常”)。-etry/catch

相反,在 中,只有相关命令会以大于 0 的退出代码退出,表示错误状态。当然,您可以检查一下,但是由于没有自动救助任何东西,因此尝试/捕获没有意义。它只是缺乏这种背景。bash

但是,您可以使用子壳来模拟纾困,这些子壳可以在您决定的某个点终止:

(
  echo "Do one thing"
  echo "Do another thing"
  if some_condition
  then
    exit 3  # <-- this is our simulated bailing out
  fi
  echo "Do yet another thing"
  echo "And do a last thing"
)   # <-- here we arrive after the simulated bailing out, and $? will be 3 (exit code)
if [ $? = 3 ]
then
  echo "Bail out detected"
fi

与其这样,您还可以尝试一个命令,如果它失败(退出代码大于 0),请保释:some_conditionif

(
  echo "Do one thing"
  echo "Do another thing"
  some_command || exit 3
  echo "Do yet another thing"
  echo "And do a last thing"
)
...

不幸的是,使用这种技术,您只能使用 255 个不同的退出代码 (1..255),并且不能使用像样的异常对象。

如果您需要更多信息来传递模拟异常,则可以使用子shell的stdout,但这有点复杂,也许是另一个问题;-)

使用上面提到的 shell 标志,您甚至可以剥离该显式语句:-eexit

(
  set -e
  echo "Do one thing"
  echo "Do another thing"
  some_command
  echo "Do yet another thing"
  echo "And do a last thing"
)
...

评论

3赞 Trent 4/20/2020
这确实应该是公认的答案,因为它是最接近 try/catch 逻辑的,因为您可以使用 shell 获得。
0赞 Eric Jones 10/15/2021
但事实并非如此。当然,有些事情会导致 bash 立即退出,我敢打赌,这就是为什么这个问题的一半读者在这里是因为他们正在寻找 try catch 的“尝试”部分,即;不要致命,而是自己处理错误,然后继续前进。例如,不会回声。但。。。您的相同子 shell 解决方案确实可以解决这个问题:确实会做回声。a=$((0/0))||echo bad(a=$((0/0)))||echo bad
0赞 Alfe 11/1/2021
我不确定我们在这里谈论的是同样的事情。点赞不会使 shell 退出;它只是结束了对这一行的解析(我同意,这是一种奇怪的行为)。它确实将退出值设置为 1。如果将这两个命令分布在两行上,则按预期工作:下一行:。a=$((0/0))a=$((0/0))if [ $? = 1 ]; then ...
6赞 Eran Ben-Natan 2/25/2014 #3

你有陷阱 http://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html 这是不一样的,但你可以用它来达到这个目的的其他技术

评论

0赞 Alfe 2/26/2014
信号实际上只与异常和尝试/捕获的概念有非常细的联系,因为它们不是程序正常控制流的一部分。但在这里提及是可以的。
188赞 Mathias Henze 8/7/2014 #4

根据我在这里找到的一些答案,我给自己做了一个小的帮助文件来为我的项目提供资源:

trycatch.sh

#!/bin/bash

function try()
{
    [[ $- = *e* ]]; SAVED_OPT_E=$?
    set +e
}

function throw()
{
    exit $1
}

function catch()
{
    export ex_code=$?
    (( $SAVED_OPT_E )) && set +e
    return $ex_code
}

function throwErrors()
{
    set -e
}

function ignoreErrors()
{
    set +e
}

以下是它在使用中的外观示例:

#!/bin/bash
export AnException=100
export AnotherException=101

# start with a try
try
(   # open a subshell !!!
    echo "do something"
    [ someErrorCondition ] && throw $AnException

    echo "do something more"
    executeCommandThatMightFail || throw $AnotherException

    throwErrors # automaticatly end the try block, if command-result is non-null
    echo "now on to something completely different"
    executeCommandThatMightFail

    echo "it's a wonder we came so far"
    executeCommandThatFailsForSure || true # ignore a single failing command

    ignoreErrors # ignore failures of commands until further notice
    executeCommand1ThatFailsForSure
    local result = $(executeCommand2ThatFailsForSure)
    [ result != "expected error" ] && throw $AnException # ok, if it's not an expected error, we want to bail out!
    executeCommand3ThatFailsForSure

    # make sure to clear $ex_code, otherwise catch * will run
    # echo "finished" does the trick for this example
    echo "finished"
)
# directly after closing the subshell you need to connect a group to the catch using ||
catch || {
    # now you can handle
    case $ex_code in
        $AnException)
            echo "AnException was thrown"
        ;;
        $AnotherException)
            echo "AnotherException was thrown"
        ;;
        *)
            echo "An unexpected exception was thrown"
            throw $ex_code # you can rethrow the "exception" causing the script to exit if not caught
        ;;
    esac
}

评论

3赞 kilianc 11/14/2015
您能告诉我们如何将 try catch 函数导入到另一个示例中吗?(我假设它们在单独的文件中)
1赞 Mathias Henze 11/24/2015
@kilianc:我只是像这样获取它:source inc/trycatch.sh。
2赞 Remy San 1/5/2017
@MathiasHenze 谢谢伙计,你的代码太酷了。但是为什么你需要一个 after the and before the block 呢?我本来以为这是一个||catch{}&&
0赞 ldmtwo 10/4/2019
(任何发现此内容的人都迟到了)从本质上讲,错误情况意味着短路 OR 尝试了第一个未返回 true 的语句,现在正在继续下一个语句。 将不起作用,因为第一个语句 () 产生 false,这意味着该语句在重言式规则中不是必需的。只有非短路 AND/OR 才能同时执行两者。if False or run_if_failed()&&trycatchfalse&any equals false
16赞 Dan Fabulich 8/29/2014 #5

正如大家所说,bash 没有正确的语言支持的 try/catch 语法。如果任何命令具有非零退出代码,则可以使用参数启动 bash 或在脚本内中止整个 bash 进程。(您也可以暂时允许失败的命令。-eset -eset +e

因此,模拟 try/catch 块的一种技术是启动一个子进程来执行 enabled 的工作。然后在主进程中,检查子进程的返回码。-e

Bash 支持 heredoc 字符串,因此您不必编写两个单独的文件来处理此问题。在下面的示例中,TRY heredoc 将在一个单独的 bash 实例中运行,并且处于启用状态,因此如果任何命令返回非零退出代码,子进程将崩溃。然后,回到主进程中,我们可以检查返回代码来处理 catch 块。-e

#!/bin/bash

set +e
bash -e <<TRY
  echo hello
  cd /does/not/exist
  echo world
TRY
if [ $? -ne 0 ]; then
  echo caught exception
fi

这不是一个适当的语言支持的 try/catch 块,但它可能会让你感到类似的痒。

84赞 niieani 5/1/2015 #6

我在 bash 中开发了一个几乎完美无缺的 try & catch 实现,它允许你编写如下代码:

try 
    echo 'Hello'
    false
    echo 'This will not be displayed'

catch 
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"

您甚至可以将 try-catch 块嵌套在它们内部!

try {
    echo 'Hello'

    try {
        echo 'Nested Hello'
        false
        echo 'This will not execute'
    } catch {
        echo "Nested Caught (@ $__EXCEPTION_LINE__)"
    }

    false
    echo 'This will not execute too'

} catch {
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
}

该代码是我的 bash 样板/框架的一部分。它进一步扩展了尝试和捕获的想法,例如使用回溯和异常(以及其他一些不错的功能)进行错误处理。

以下是负责 try & catch 的代码:

set -o pipefail
shopt -s expand_aliases
declare -ig __oo__insideTryCatch=0

# if try-catch is nested, then set +e before so the parent handler doesn't catch us
alias try="[[ \$__oo__insideTryCatch -gt 0 ]] && set +e;
           __oo__insideTryCatch+=1; ( set -e;
           trap \"Exception.Capture \${LINENO}; \" ERR;"
alias catch=" ); Exception.Extract \$? || "

Exception.Capture() {
    local script="${BASH_SOURCE[1]#./}"

    if [[ ! -f /tmp/stored_exception_source ]]; then
        echo "$script" > /tmp/stored_exception_source
    fi
    if [[ ! -f /tmp/stored_exception_line ]]; then
        echo "$1" > /tmp/stored_exception_line
    fi
    return 0
}

Exception.Extract() {
    if [[ $__oo__insideTryCatch -gt 1 ]]
    then
        set -e
    fi

    __oo__insideTryCatch+=-1

    __EXCEPTION_CATCH__=( $(Exception.GetLastException) )

    local retVal=$1
    if [[ $retVal -gt 0 ]]
    then
        # BACKWARDS COMPATIBILE WAY:
        # export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-1)]}"
        # export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-2)]}"
        export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[-1]}"
        export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[-2]}"
        export __EXCEPTION__="${__EXCEPTION_CATCH__[@]:0:(${#__EXCEPTION_CATCH__[@]} - 2)}"
        return 1 # so that we may continue with a "catch"
    fi
}

Exception.GetLastException() {
    if [[ -f /tmp/stored_exception ]] && [[ -f /tmp/stored_exception_line ]] && [[ -f /tmp/stored_exception_source ]]
    then
        cat /tmp/stored_exception
        cat /tmp/stored_exception_line
        cat /tmp/stored_exception_source
    else
        echo -e " \n${BASH_LINENO[1]}\n${BASH_SOURCE[2]#./}"
    fi

    rm -f /tmp/stored_exception /tmp/stored_exception_line /tmp/stored_exception_source
    return 0
}

随意使用、分叉和贡献 - 它在 GitHub 上。

评论

1赞 niieani 7/26/2015
@erm3nda 很高兴听到这个消息!我想我在发布这篇文章后已经消除了一些错误,所以请查看 GitHub 以获取更新(您需要包含03_exception.sh和04_try_catch.sh)。据我所知,当前版本几乎是防弹的。
0赞 Felipe 3/29/2017
很好!我将在我的项目中使用。我在 5 分钟内开始工作,我的 centos 已经使用 bash 4.2.46
1赞 Kan Li 7/15/2018
这里有一个基本问题:如果你在 try 块中更改一个变量,它不会在外面看到,因为它在子 shell 中运行。
1赞 niieani 8/2/2018
@KanLi正确。如果您关心 try/catch 的输出,您可以像这样捕获它:my_output=$(try { code...; } catch { code...; })
0赞 Ben Creasy 9/24/2018
在最新版本中,EXCEPTION_LINE似乎已重命名为BACKTRACE_LINE github.com/niieani/bash-oo-framework#using-try--catch
36赞 Mark K Cowan 4/15/2017 #7

您可以使用:trap

try { block A } catch { block B } finally { block C }

翻译为:

(
  set -Ee
  function _catch {
    block B
    exit 0  # optional; use if you don't want to propagate (rethrow) error to outer shell
  }
  function _finally {
    block C
  }
  trap _catch ERR
  trap _finally EXIT
  block A
)

评论

0赞 Mark K Cowan 5/8/2017
我认为你也想要标志,所以陷阱传播到函数-E
0赞 sanjarcode 1/30/2023
你能举个例子吗?
34赞 Kostanos 10/6/2017 #8

有很多类似的解决方案可能有效。以下是完成尝试/捕捉的简单而有效的方法,并在评论中进行了解释。

#!/bin/bash

function a() {
  # do some stuff here
}
function b() {
  # do more stuff here
}

# this subshell is a scope of try
# try
(
  # this flag will make to exit from current subshell on any error
  # inside it (all functions run inside will also break on any error)
  set -e
  a
  b
  # do more stuff here
)
# and here we catch errors
# catch
errorCode=$?
if [ $errorCode -ne 0 ]; then
  echo "We have an error"
  # We exit the all script with the same error, if you don't want to
  # exit it and continue, just delete this line.
  exit $errorCode
fi

评论

1赞 David Roth 10/7/2021
在此线程中给出最佳解决方案。简单,但有效!可以很容易地集成到现有脚本中。谢谢你的分享!!
0赞 davychhouk 2/3/2022
这太棒了。像魅力一样工作。谢谢:)
0赞 Shōgun8 3/7/2023
如果不执行错误的函数并产生与原本输出相同的输出,这似乎不会退出。这只是在它的末尾添加了“我们有一个错误”。
0赞 Kostanos 3/7/2023
你能更具体一点吗?我从未发现此代码有任何问题......
1赞 syberghost 10/6/2017 #9

我使用的一个非常简单的东西:

try() {
    "$@" || (e=$?; echo "$@" > /dev/stderr; exit $e)
}

评论

1赞 codeforester 5/12/2018
由于 的右侧是 ,它将在子 shell 中运行并退出,而不会导致主 shell 退出。请改用分组。||(){ }
10赞 Davidson Lima 1/3/2018 #10

您可以执行以下操作:

#!/bin/bash
if <command> ; then # TRY
    <do-whatever-you-want>
else # CATCH
    echo 'Exception'
    <do-whatever-you-want>
fi

评论

0赞 petrosmm 10/24/2023
可能是这里最好的简单答案,有谁知道是否可以设置为一个衬里?所以它可以在 Docker 中使用RUN ...
0赞 David Anderson 3/11/2019 #11

下面是在 bash 中实现的脚本示例。try/catch/finally

与此问题的其他答案一样,必须在退出子进程后捕获异常。

示例脚本首先创建一个匿名 fifo,该 fifo 用于将字符串消息从最近的块或末尾传递到最近的块的末尾。在这里,消息从 fifo 中删除并放置在数组变量中。状态通过和命令返回,并放置在不同的变量中。要输入块,此状态不得为零。输入块的其他要求将作为参数传递。如果到达块的末尾,则状态设置为零。如果到达块的末尾并且状态仍为非零,则执行包含消息和状态的隐式抛出。该脚本需要调用包含未经处理的异常处理程序的函数。command exceptionthrowtryreturnexitcatchcatchcatchfinallytrycatchfinally

该命令的语法如下所示。trycatchfinally

trycatchfinally [-cde] [-h ERR_handler] [-k] [-o debug_file] [-u unhandled_handler] [-v variable] fifo function

该选项将调用堆栈添加到异常消息中。
该选项启用调试输出。
该选项启用命令例外。
该选项允许用户替换自己的命令异常处理程序。
该选项将调用堆栈添加到调试输出。
该选项将替换默认输出文件,即 。
该选项允许用户替换其自己的未处理的异常处理程序。
该选项允许用户通过使用命令替换来传回值。
是 fifo 文件名。
该函数作为子进程调用。
-c-d-e-h-k-o/dev/fd/2-u-vfifofunctiontrycatchfinally

注意:删除了这些选项以简化脚本。cdko

该命令的语法如下所示。catch

catch [[-enoprt] list ...] ...

选项定义如下。第一个列表的值是状态。后续值是消息。如果消息数大于 列表,则忽略其余消息。

-emeans(该值必须与列表中的至少一个字符串匹配) means(该值不能与列表中的任何字符串匹配) means(该值不能与列表中的任何模式匹配) means(该值必须与列表中的至少一个模式匹配) means(该值必须与列表中的至少一个扩展正则表达式匹配)




means(该值不能与列表中的扩展正则表达式)
[[ $value == "$string" ]]-n[[ $value != "$string" ]]-o[[ $value != $pattern ]]-p[[ $value == $pattern ]]-r[[ $value =~ $regex ]]-t[[ ! $value =~ $regex ]]

脚本如下。为了简化此答案的脚本,删除了大部分错误检查。这样一来,尺寸就减少了 64%。这个脚本的完整副本可以在我的另一个答案中找到。try/catch/finally

shopt -s expand_aliases
alias try='{ common.Try'
alias yrt='EchoExitStatus; common.yrT; }'
alias catch='{ while common.Catch'
alias hctac='common.hctaC; done; }'
alias finally='{ common.Finally'
alias yllanif='common.yllaniF; }'

DefaultErrHandler() {
    echo "Orginal Status: $common_status"
    echo "Exception Type: ERR"
}

exception() {
    let "common_status = 10#$1"
    shift
    common_messages=()
    for message in "$@"; do
        common_messages+=("$message")
    done
}

throw() {
    local "message"
    if [[ $# -gt 0 ]]; then
        let "common_status = 10#$1"
        shift
        for message in "$@"; do
            echo "$message" >"$common_fifo"
        done
    elif [[ ${#common_messages[@]} -gt 0 ]]; then
        for message in "${common_messages[@]}"; do
            echo "$message" >"$common_fifo"
        done
    fi
    chmod "0400" "$common_fifo"
    exit "$common_status"
}

common.ErrHandler() {
    common_status=$?
    trap ERR
    if [[ -w "$common_fifo" ]]; then
        if [[ $common_options != *e* ]]; then
            common_status="0"
            return
        fi
        eval "${common_errHandler:-} \"${BASH_LINENO[0]}\" \"${BASH_SOURCE[1]}\" \"${FUNCNAME[1]}\" >$common_fifo <$common_fifo"
        chmod "0400" "$common_fifo"
    fi
    if [[ common_trySubshell -eq BASH_SUBSHELL ]]; then
        return   
    else
        exit "$common_status"
    fi
}

common.Try() {
    common_status="0"
    common_subshell="$common_trySubshell"
    common_trySubshell="$BASH_SUBSHELL"
    common_messages=()
}

common.yrT() {
    local "status=$?"
    if [[ common_status -ne 0 ]]; then    
        local "message=" "eof=TRY_CATCH_FINALLY_END_OF_MESSAGES_$RANDOM"
        chmod "0600" "$common_fifo"
        echo "$eof" >"$common_fifo"
        common_messages=()
        while read "message"; do
            [[ $message != *$eof ]] || break
            common_messages+=("$message")
        done <"$common_fifo"
    fi
    common_trySubshell="$common_subshell"
}

common.Catch() {
    [[ common_status -ne 0 ]] || return "1"
    local "parameter" "pattern" "value"
    local "toggle=true" "compare=p" "options=$-"
    local -i "i=-1" "status=0"
    set -f
    for parameter in "$@"; do
        if "$toggle"; then
            toggle="false"
            if [[ $parameter =~ ^-[notepr]$ ]]; then
                compare="${parameter#-}"
                continue 
            fi
        fi
        toggle="true"
        while "true"; do
            eval local "patterns=($parameter)"
            if [[ ${#patterns[@]} -gt 0 ]]; then
                for pattern in "${patterns[@]}"; do
                    [[ i -lt ${#common_messages[@]} ]] || break
                    if [[ i -lt 0 ]]; then
                        value="$common_status"
                    else
                        value="${common_messages[i]}"
                    fi
                    case $compare in
                    [ne]) [[ ! $value == "$pattern" ]] || break 2;;
                    [op]) [[ ! $value == $pattern ]] || break 2;;
                    [tr]) [[ ! $value =~ $pattern ]] || break 2;;
                    esac
                done
            fi
            if [[ $compare == [not] ]]; then
                let "++i,1"
                continue 2
            else
                status="1"
                break 2
            fi
        done
        if [[ $compare == [not] ]]; then
            status="1"
            break
        else
            let "++i,1"
        fi
    done
    [[ $options == *f* ]] || set +f
    return "$status"
} 

common.hctaC() {
    common_status="0"
}

common.Finally() {
    :
}

common.yllaniF() {
    [[ common_status -eq 0 ]] || throw
}

caught() {
    [[ common_status -eq 0 ]] || return 1
}

EchoExitStatus() {
    return "${1:-$?}"
}

EnableThrowOnError() {
    [[ $common_options == *e* ]] || common_options+="e"
}

DisableThrowOnError() {
    common_options="${common_options/e}"
}

GetStatus() {
    echo "$common_status"
}

SetStatus() {
    let "common_status = 10#$1"
}

GetMessage() {
    echo "${common_messages[$1]}"
}

MessageCount() {
    echo "${#common_messages[@]}"
}

CopyMessages() {
    if [[ ${#common_messages} -gt 0 ]]; then
        eval "$1=(\"\${common_messages[@]}\")"
    else
        eval "$1=()"
    fi
}

common.GetOptions() {
    local "opt"
    let "OPTIND = 1"
    let "OPTERR = 0"
    while getopts ":cdeh:ko:u:v:" opt "$@"; do
        case $opt in
        e)  [[ $common_options == *e* ]] || common_options+="e";;
        h)  common_errHandler="$OPTARG";;
        u)  common_unhandled="$OPTARG";;
        v)  common_command="$OPTARG";;
        esac
    done
    shift "$((OPTIND - 1))"
    common_fifo="$1"
    shift
    common_function="$1"
    chmod "0600" "$common_fifo"
}

DefaultUnhandled() {
    local -i "i"
    echo "-------------------------------------------------"
    echo "TryCatchFinally: Unhandeled exception occurred"
    echo "Status: $(GetStatus)"
    echo "Messages:"
    for ((i=0; i<$(MessageCount); i++)); do
        echo "$(GetMessage "$i")"
    done
    echo "-------------------------------------------------"
}

TryCatchFinally() {
    local "common_errHandler=DefaultErrHandler"
    local "common_unhandled=DefaultUnhandled"
    local "common_options="
    local "common_fifo="
    local "common_function="
    local "common_flags=$-"
    local "common_trySubshell=-1"
    local "common_subshell"
    local "common_status=0"
    local "common_command="
    local "common_messages=()"
    local "common_handler=$(trap -p ERR)"
    [[ -n $common_handler ]] || common_handler="trap ERR"
    common.GetOptions "$@"
    shift "$((OPTIND + 1))"
    [[ -z $common_command ]] || common_command+="=$"
    common_command+='("$common_function" "$@")'
    set -E
    set +e
    trap "common.ErrHandler" ERR
    try
        eval "$common_command"
    yrt
    catch; do
        "$common_unhandled" >&2
    hctac
    [[ $common_flags == *E* ]] || set +E
    [[ $common_flags != *e* ]] || set -e
    [[ $common_flags != *f* || $- == *f* ]] || set -f
    [[ $common_flags == *f* || $- != *f* ]] || set +f
    eval "$common_handler"
}

下面是一个示例,它假定上述脚本存储在名为 .该文件包含此答案中描述的脚本。假设命名的文件不存在,因此会导致发生异常。该命令的错误消息输出会自动被禁止显示,直到进入相应的块内。simplemakefifo4444kkkkkls 4444kkkkkcatch

#!/bin/bash
#

if [[ $0 != ${BASH_SOURCE[0]} ]]; then
    bash "${BASH_SOURCE[0]}" "$@"
    return
fi

source simple
source makefifo

MyFunction3() {
    echo "entered MyFunction3" >&4
    echo "This is from MyFunction3"
    ls 4444kkkkk
    echo "leaving MyFunction3" >&4
}

MyFunction2() {
    echo "entered MyFunction2" >&4
    value="$(MyFunction3)"
    echo "leaving MyFunction2" >&4
}

MyFunction1() {
    echo "entered MyFunction1" >&4
    local "flag=false"
    try 
    (
        echo "start of try" >&4
        MyFunction2
        echo "end of try" >&4
    )
    yrt
    catch "[1-3]" "*" "Exception\ Type:\ ERR"; do
        echo 'start of catch "[1-3]" "*" "Exception\ Type:\ ERR"'
        local -i "i"
        echo "-------------------------------------------------"
        echo "Status: $(GetStatus)"
        echo "Messages:"
        for ((i=0; i<$(MessageCount); i++)); do
            echo "$(GetMessage "$i")"
        done
        echo "-------------------------------------------------"
        break
        echo 'end of catch "[1-3]" "*" "Exception\ Type:\ ERR"'
    hctac >&4
    catch "1 3 5" "*" -n "Exception\ Type:\ ERR"; do
        echo 'start of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"'
        echo "-------------------------------------------------"
        echo "Status: $(GetStatus)"
        [[ $(MessageCount) -le 1 ]] || echo "$(GetMessage "1")"
        echo "-------------------------------------------------"
        break
        echo 'end of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"'
    hctac >&4
    catch; do
        echo 'start of catch' >&4
        echo "failure"
        flag="true"
        echo 'end of catch' >&4
    hctac
    finally
        echo "in finally"
    yllanif >&4
    "$flag" || echo "success"
    echo "leaving MyFunction1" >&4
} 2>&6

ErrHandler() {
    echo "EOF"
    DefaultErrHandler "$@"
    echo "Function: $3"
    while read; do
        [[ $REPLY != *EOF ]] || break
        echo "$REPLY"
    done
}

set -u
echo "starting" >&2
MakeFIFO "6"
TryCatchFinally -e -h ErrHandler -o /dev/fd/4 -v result /dev/fd/6 MyFunction1 4>&2
echo "result=$result"
exec >&6-

上面的脚本是使用 .运行此脚本的输出如下所示。GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)

starting
entered MyFunction1
start of try
entered MyFunction2
entered MyFunction3
start of catch "[1-3]" "*" "Exception\ Type:\ ERR"
-------------------------------------------------
Status: 1
Messages:
Orginal Status: 1
Exception Type: ERR
Function: MyFunction3
ls: 4444kkkkk: No such file or directory
-------------------------------------------------
start of catch
end of catch
in finally
leaving MyFunction1
result=failure

另一个使用 a 的示例可以通过将函数替换为如下所示的脚本来创建。throwMyFunction3

MyFunction3() {
    echo "entered MyFunction3" >&4
    echo "This is from MyFunction3"
    throw "3" "Orginal Status: 3" "Exception Type: throw"
    echo "leaving MyFunction3" >&4
}

该命令的语法如下所示。如果不存在任何参数,则改用变量中存储的状态和消息。throw

throw [status] [message ...]

执行修改后的脚本的输出如下所示。

starting
entered MyFunction1
start of try
entered MyFunction2
entered MyFunction3
start of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"
-------------------------------------------------
Status: 3
Exception Type: throw
-------------------------------------------------
start of catch
end of catch
in finally
leaving MyFunction1
result=failure
1赞 David Anderson 3/13/2019 #12

以下是我的另一个答案中使用的简化脚本的完整副本。除了额外的错误检查之外,还有一个别名,允许用户更改现有别名的名称。语法如下。如果省略该参数,则删除别名。new_alias

ChangeAlias old_alias [new_alias]

完整的脚本如下。

common.GetAlias() {
    local "oldname=${1:-0}"
    if [[ $oldname =~ ^[0-9]+$ && oldname+1 -lt ${#FUNCNAME[@]} ]]; then
        oldname="${FUNCNAME[oldname + 1]}"
    fi
    name="common_${oldname#common.}"
    echo "${!name:-$oldname}"
}

common.Alias() {
    if [[ $# -ne 2 || -z $1 || -z $2 ]]; then
        echo "$(common.GetAlias): The must be only two parameters of nonzero length" >&2
        return 1;
    fi
    eval "alias $1='$2'"
    local "f=${2##*common.}"
    f="${f%%;*}"
    local "v=common_$f"
    f="common.$f"
    if [[ -n ${!v:-} ]]; then
        echo "$(common.GetAlias): $1: Function \`$f' already paired with name \`${!v}'" >&2
        return 1;
    fi
    shopt -s expand_aliases
    eval "$v=\"$1\""
}

common.ChangeAlias() {
    if [[ $# -lt 1 || $# -gt 2 ]]; then
        echo "usage: $(common.GetAlias) old_name [new_name]" >&2
        return "1"
    elif ! alias "$1" &>"/dev/null"; then
        echo "$(common.GetAlias): $1: Name not found" >&2
        return 1;
    fi
    local "s=$(alias "$1")" 
    s="${s#alias $1=\'}"
    s="${s%\'}"
    local "f=${s##*common.}"
    f="${f%%;*}"
    local "v=common_$f"
    f="common.$f"
    if [[ ${!v:-} != "$1" ]]; then
        echo "$(common.GetAlias): $1: Name not paired with a function \`$f'" >&2
        return 1;
    elif [[ $# -gt 1 ]]; then
        eval "alias $2='$s'"
        eval "$v=\"$2\""
    else
        unset "$v"
    fi
    unalias "$1"
}

common.Alias exception             'common.Exception'
common.Alias throw                 'common.Throw'
common.Alias try                   '{ if common.Try; then'
common.Alias yrt                   'common.EchoExitStatus; fi; common.yrT; }'
common.Alias catch                 '{ while common.Catch'
common.Alias hctac                 'common.hctaC -r; done; common.hctaC; }'
common.Alias finally               '{ if common.Finally; then'
common.Alias yllanif               'fi; common.yllaniF; }'
common.Alias caught                'common.Caught'
common.Alias EchoExitStatus        'common.EchoExitStatus'
common.Alias EnableThrowOnError    'common.EnableThrowOnError'
common.Alias DisableThrowOnError   'common.DisableThrowOnError'
common.Alias GetStatus             'common.GetStatus'
common.Alias SetStatus             'common.SetStatus'
common.Alias GetMessage            'common.GetMessage'
common.Alias MessageCount          'common.MessageCount'
common.Alias CopyMessages          'common.CopyMessages'
common.Alias TryCatchFinally       'common.TryCatchFinally'
common.Alias DefaultErrHandler     'common.DefaultErrHandler'
common.Alias DefaultUnhandled      'common.DefaultUnhandled'
common.Alias CallStack             'common.CallStack'
common.Alias ChangeAlias           'common.ChangeAlias'
common.Alias TryCatchFinallyAlias  'common.Alias'

common.CallStack() {
    local -i "i" "j" "k" "subshell=${2:-0}" "wi" "wl" "wn"
    local "format= %*s  %*s  %-*s  %s\n" "name"
    eval local "lineno=('' ${BASH_LINENO[@]})"
    for (( i=${1:-0},j=wi=wl=wn=0; i<${#FUNCNAME[@]}; ++i,++j )); do  
        name="$(common.GetAlias "$i")"
        let "wi = ${#j} > wi ? wi = ${#j} : wi"
        let "wl = ${#lineno[i]} > wl ? wl = ${#lineno[i]} : wl"
        let "wn = ${#name} > wn ? wn = ${#name} : wn"
    done
    for (( i=${1:-0},j=0; i<${#FUNCNAME[@]}; ++i,++j )); do
        ! let "k = ${#FUNCNAME[@]} - i - 1"
        name="$(common.GetAlias "$i")"
        printf "$format" "$wi" "$j" "$wl" "${lineno[i]}" "$wn" "$name" "${BASH_SOURCE[i]}"
    done
}

common.Echo() {
    [[ $common_options != *d* ]] || echo "$@" >"$common_file"
}

common.DefaultErrHandler() {
    echo "Orginal Status: $common_status"
    echo "Exception Type: ERR"
}

common.Exception() {
    common.TryCatchFinallyVerify || return
    if [[ $# -eq 0 ]]; then
        echo "$(common.GetAlias): At least one parameter is required" >&2
        return "1"         
    elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ]]; then
        echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
        return "1"
    fi
    let "common_status = 10#$1"
    shift
    common_messages=()
    for message in "$@"; do
        common_messages+=("$message")
    done
    if [[ $common_options == *c* ]]; then
        echo "Call Stack:" >"$common_fifo"
        common.CallStack "2" >"$common_fifo"
    fi
}

common.Throw() {
    common.TryCatchFinallyVerify || return
    local "message"
    if ! common.TryCatchFinallyExists; then
        echo "$(common.GetAlias): No Try-Catch-Finally exists" >&2
        return "1"        
    elif [[ $# -eq 0 && common_status -eq 0 ]]; then
        echo "$(common.GetAlias): No previous unhandled exception" >&2 
        return "1"
    elif [[ $# -gt 0 && ( ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ) ]]; then
        echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
        return "1"
    fi
    common.Echo -n "In Throw ?=$common_status "
    common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL #=$#"
    if [[ $common_options == *k* ]]; then
        common.CallStack "2" >"$common_file"
    fi
    if [[ $# -gt 0 ]]; then
        let "common_status = 10#$1"
        shift
        for message in "$@"; do
            echo "$message" >"$common_fifo"
        done
        if [[ $common_options == *c* ]]; then
            echo "Call Stack:" >"$common_fifo"
            common.CallStack "2" >"$common_fifo"
        fi
    elif [[ ${#common_messages[@]} -gt 0 ]]; then
        for message in "${common_messages[@]}"; do
            echo "$message" >"$common_fifo"
        done
    fi
    chmod "0400" "$common_fifo"
    common.Echo "Still in Throw $=$common_status subshell=$BASH_SUBSHELL #=$# -=$-"
    exit "$common_status"
}

common.ErrHandler() {
    common_status=$?
    trap ERR
    common.Echo -n "In ErrHandler ?=$common_status debug=$common_options "
    common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL order=$common_order"
    if [[ -w "$common_fifo" ]]; then
        if [[ $common_options != *e* ]]; then
            common.Echo "ErrHandler is ignoring"
            common_status="0"
            return "$common_status" # value is ignored
        fi
        if [[ $common_options == *k* ]]; then
            common.CallStack "2" >"$common_file"
        fi
        common.Echo "Calling ${common_errHandler:-}"
        eval "${common_errHandler:-} \"${BASH_LINENO[0]}\" \"${BASH_SOURCE[1]}\" \"${FUNCNAME[1]}\" >$common_fifo <$common_fifo"
        if [[ $common_options == *c* ]]; then
            echo "Call Stack:" >"$common_fifo"
            common.CallStack "2" >"$common_fifo"
        fi
        chmod "0400" "$common_fifo"
    fi
    common.Echo "Still in ErrHandler $=$common_status subshell=$BASH_SUBSHELL -=$-"
    if [[ common_trySubshell -eq BASH_SUBSHELL ]]; then
        return "$common_status" # value is ignored   
    else
        exit "$common_status"
    fi
}

common.Token() {
    local "name"
    case $1 in
    b) name="before";;
    t) name="$common_Try";;
    y) name="$common_yrT";;
    c) name="$common_Catch";;
    h) name="$common_hctaC";;
    f) name="$common_yllaniF";;
    l) name="$common_Finally";;
    *) name="unknown";;
    esac
    echo "$name"
}

common.TryCatchFinallyNext() {
    common.ShellInit
    local "previous=$common_order" "errmsg"
    common_order="$2"
    if [[ $previous != $1 ]]; then
        errmsg="${BASH_SOURCE[2]}: line ${BASH_LINENO[1]}: syntax error_near unexpected token \`$(common.Token "$2")'"
        echo "$errmsg" >&2
        [[ /dev/fd/2 -ef $common_file ]] || echo "$errmsg" >"$common_file"
        kill -s INT 0
        return "1"        
    fi
}

common.ShellInit() {
    if [[ common_initSubshell -ne BASH_SUBSHELL ]]; then
        common_initSubshell="$BASH_SUBSHELL"
        common_order="b"
    fi
}

common.Try() {
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[byhl]" "t" || return 
    common_status="0"
    common_subshell="$common_trySubshell"
    common_trySubshell="$BASH_SUBSHELL"
    common_messages=()
    common.Echo "-------------> Setting try=$common_trySubshell at subshell=$BASH_SUBSHELL"
}

common.yrT() {
    local "status=$?"
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[t]" "y" || return 
    common.Echo -n "Entered yrT ?=$status status=$common_status "
    common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL"
    if [[ common_status -ne 0 ]]; then    

        common.Echo "Build message array. ?=$common_status, subshell=$BASH_SUBSHELL"
        local "message=" "eof=TRY_CATCH_FINALLY_END_OF_MESSAGES_$RANDOM"
        chmod "0600" "$common_fifo"
        echo "$eof" >"$common_fifo"
        common_messages=()
        while read "message"; do

            common.Echo "----> $message"

            [[ $message != *$eof ]] || break
            common_messages+=("$message")
        done <"$common_fifo"
    fi

    common.Echo "In ytT status=$common_status"
    common_trySubshell="$common_subshell"
}

common.Catch() {
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[yh]" "c" || return 
    [[ common_status -ne 0 ]] || return "1"
    local "parameter" "pattern" "value"
    local "toggle=true" "compare=p" "options=$-"
    local -i "i=-1" "status=0"
    set -f
    for parameter in "$@"; do
        if "$toggle"; then
            toggle="false"
            if [[ $parameter =~ ^-[notepr]$ ]]; then
                compare="${parameter#-}"
                continue 
            fi
        fi
        toggle="true"
        while "true"; do
            eval local "patterns=($parameter)"
            if [[ ${#patterns[@]} -gt 0 ]]; then
                for pattern in "${patterns[@]}"; do
                    [[ i -lt ${#common_messages[@]} ]] || break
                    if [[ i -lt 0 ]]; then
                        value="$common_status"
                    else
                        value="${common_messages[i]}"
                    fi
                    case $compare in
                    [ne]) [[ ! $value == "$pattern" ]] || break 2;;
                    [op]) [[ ! $value == $pattern ]] || break 2;;
                    [tr]) [[ ! $value =~ $pattern ]] || break 2;;
                    esac
                done
            fi
            if [[ $compare == [not] ]]; then
                let "++i,1"
                continue 2
            else
                status="1"
                break 2
            fi
        done
        if [[ $compare == [not] ]]; then
            status="1"
            break
        else
            let "++i,1"
        fi
    done
    [[ $options == *f* ]] || set +f
    return "$status"
} 

common.hctaC() {
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[c]" "h" || return 
    [[ $# -ne 1 || $1 != -r ]] || common_status="0"
}

common.Finally() {
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[ych]" "f" || return 
}

common.yllaniF() {
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[f]" "l" || return 
    [[ common_status -eq 0 ]] || common.Throw
}

common.Caught() {
    common.TryCatchFinallyVerify || return
    [[ common_status -eq 0 ]] || return 1
}

common.EchoExitStatus() {
    return "${1:-$?}"
}

common.EnableThrowOnError() {
    common.TryCatchFinallyVerify || return
    [[ $common_options == *e* ]] || common_options+="e"
}

common.DisableThrowOnError() {
    common.TryCatchFinallyVerify || return
    common_options="${common_options/e}"
}

common.GetStatus() {
    common.TryCatchFinallyVerify || return
    echo "$common_status"
}

common.SetStatus() {
    common.TryCatchFinallyVerify || return
    if [[ $# -ne 1 ]]; then
        echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
        return "1"         
    elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ]]; then
        echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
        return "1"
    fi
    let "common_status = 10#$1"
}

common.GetMessage() {
    common.TryCatchFinallyVerify || return
    local "upper=${#common_messages[@]}"
    if [[ upper -eq 0 ]]; then
        echo "$(common.GetAlias): $1: There are no messages" >&2
        return "1"
    elif [[ $# -ne 1 ]]; then
        echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
        return "1"         
    elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -ge upper ]]; then
        echo "$(common.GetAlias): $1: First parameter was an invalid index" >&2
        return "1"
    fi
    echo "${common_messages[$1]}"
}

common.MessageCount() {
    common.TryCatchFinallyVerify || return
    echo "${#common_messages[@]}"
}

common.CopyMessages() {
    common.TryCatchFinallyVerify || return
    if [[ $# -ne 1 ]]; then
        echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
        return "1"         
    elif [[ ${#common_messages} -gt 0 ]]; then
        eval "$1=(\"\${common_messages[@]}\")"
    else
        eval "$1=()"
    fi
}

common.TryCatchFinallyExists() {
    [[ ${common_fifo:-u} != u ]]
}

common.TryCatchFinallyVerify() {
    local "name"
    if ! common.TryCatchFinallyExists; then
        echo "$(common.GetAlias "1"): No Try-Catch-Finally exists" >&2
        return "2"        
    fi
}

common.GetOptions() {
    local "opt"
    local "name=$(common.GetAlias "1")"
    if common.TryCatchFinallyExists; then
        echo "$name: A Try-Catch-Finally already exists" >&2
        return "1"        
    fi
    let "OPTIND = 1"
    let "OPTERR = 0"
    while getopts ":cdeh:ko:u:v:" opt "$@"; do
        case $opt in
        c)  [[ $common_options == *c* ]] || common_options+="c";;
        d)  [[ $common_options == *d* ]] || common_options+="d";;
        e)  [[ $common_options == *e* ]] || common_options+="e";;
        h)  common_errHandler="$OPTARG";;
        k)  [[ $common_options == *k* ]] || common_options+="k";;
        o)  common_file="$OPTARG";;
        u)  common_unhandled="$OPTARG";;
        v)  common_command="$OPTARG";;
        \?) #echo "Invalid option: -$OPTARG" >&2
            echo "$name: Illegal option: $OPTARG" >&2
            return "1";;
        :)  echo "$name: Option requires an argument: $OPTARG" >&2
            return "1";;
        *)  echo "$name: An error occurred while parsing options." >&2
            return "1";;
        esac
    done

    shift "$((OPTIND - 1))"
    if [[ $# -lt 1 ]]; then
        echo "$name: The fifo_file parameter is missing" >&2
        return "1"
    fi
    common_fifo="$1"
    if [[ ! -p $common_fifo ]]; then
        echo "$name: $1: The fifo_file is not an open FIFO" >&2
        return "1"  
    fi

    shift
    if [[ $# -lt 1 ]]; then
        echo "$name: The function parameter is missing" >&2
        return "1"
    fi
    common_function="$1"
    if ! chmod "0600" "$common_fifo"; then
        echo "$name: $common_fifo: Can not change file mode to 0600" >&2
        return "1"
    fi

    local "message=" "eof=TRY_CATCH_FINALLY_END_OF_FILE_$RANDOM"
    { echo "$eof" >"$common_fifo"; } 2>"/dev/null"
    if [[ $? -ne 0 ]]; then
        echo "$name: $common_fifo: Can not write" >&2
        return "1"
    fi   
    { while [[ $message != *$eof ]]; do
        read "message"
    done <"$common_fifo"; } 2>"/dev/null"
    if [[ $? -ne 0 ]]; then
        echo "$name: $common_fifo: Can not read" >&2
        return "1"
    fi   

    return "0"
}

common.DefaultUnhandled() {
    local -i "i"
    echo "-------------------------------------------------"
    echo "$(common.GetAlias "common.TryCatchFinally"): Unhandeled exception occurred"
    echo "Status: $(GetStatus)"
    echo "Messages:"
    for ((i=0; i<$(MessageCount); i++)); do
        echo "$(GetMessage "$i")"
    done
    echo "-------------------------------------------------"
}

common.TryCatchFinally() {
    local "common_file=/dev/fd/2"
    local "common_errHandler=common.DefaultErrHandler"
    local "common_unhandled=common.DefaultUnhandled"
    local "common_options="
    local "common_fifo="
    local "common_function="
    local "common_flags=$-"
    local "common_trySubshell=-1"
    local "common_initSubshell=-1"
    local "common_subshell"
    local "common_status=0"
    local "common_order=b"
    local "common_command="
    local "common_messages=()"
    local "common_handler=$(trap -p ERR)"
    [[ -n $common_handler ]] || common_handler="trap ERR"

    common.GetOptions "$@" || return "$?"
    shift "$((OPTIND + 1))"

    [[ -z $common_command ]] || common_command+="=$"
    common_command+='("$common_function" "$@")'

    set -E
    set +e
    trap "common.ErrHandler" ERR
    if true; then
        common.Try 
        eval "$common_command"
        common.EchoExitStatus
        common.yrT
    fi
    while common.Catch; do
        "$common_unhandled" >&2
        break
        common.hctaC -r
    done
    common.hctaC
    [[ $common_flags == *E* ]] || set +E
    [[ $common_flags != *e* ]] || set -e
    [[ $common_flags != *f* || $- == *f* ]] || set -f
    [[ $common_flags == *f* || $- != *f* ]] || set +f
    eval "$common_handler"
    return "$((common_status?2:0))"
}
2赞 Axel Heider 7/2/2020 #13

我可以在“bash -ue”模式下推荐这个:

set -ue
   
false && RET=$? || RET=$? 
echo "expecting 1, got ${RET}"
true && RET=$? || RET=$? 
echo "expecting 0, got ${RET}"

echo "test try...catch"
false && RET=$? || RET=$? 
if [ ${RET} -ne 0 ]; then
  echo "caught error ${RET}"
fi

echo "beware, using '||' before '&&' fails"
echo "  -> memory aid: [A]nd before [O]r in the alphabet"
false || RET=$? && RET=$? 
echo "expecting 1, got ${RET}"
true || RET=$? && RET=$? 
echo "expecting 0, got ${RET}"

评论

0赞 gndps 11/18/2022
一些冗长的内容确实会有所帮助
3赞 joel 9/24/2020 #14

假设有问题的终端应用程序被命名为“app”(并且您希望它出现在您的$PATH中):

if [[ ! `which app` ]]; then
    # run code if app not installed
else
    # run code if app is installed
fi
9赞 Sean Mayo 10/2/2021 #15

我使用这样的东西:

YOUR COMMAND HERE
EXITCODE=$?
if [ "$EXITCODE" -ne "0" ]; then
    #this is the catch part
    echo "uh oh"
    exit $EXITCODE
fi

这只是检查命令的退出代码并将其与零进行比较 (表示命令运行成功)

4赞 Aleks 10/12/2022 #16

为了捎带肖恩·梅奥的答案,你可以这样做

function check {
EXITCODE=$?
if [ "$EXITCODE" -ne "0" ]; then
    #this is the catch part
    echo "uh oh"
    exit $EXITCODE
fi
}

YOUR COMMAND HERE
check
YOUR COMMAND HERE
check
YOUR COMMAND HERE
check
YOUR COMMAND HERE
check

效果很好

0赞 openCivilisation 11/18/2023 #17

我更喜欢使用陷阱来解决这个问题,因为它对格式的干扰最小。

我正在复制 Dirk Avery 的这篇精彩读物中一个使用 trap 的示例:https://medium.com/@dirk.avery/the-bash-trap-trap-ce6083f36700

#!/bin/bash
set -e
trap 'catch $? $LINENO' EXIT
catch() {
  echo "catching!"
  if [ "$1" != "0" ]; then
    # error handling goes here
    echo "Error $1 occurred on $2"
  fi
}
simple() {
  badcommand
  echo "Hi from simple()!"
}
simple
echo "After simple call"

这将输出:

./script.sh: line 13: badcommand: command not found
catching!
Error 127 occurred on 12
0赞 harshit srivastava 11/21/2023 #18

我将 bash 脚本保存为 test.sh,并在顶部定义了 set -e。

set -e 
mkdir first
cd first
mdir inside #this command will fail giving an exit code not equal to 0.

并在 Jenkins Pipeline 中捕获结果,例如

 TestResult = command "sh test.sh"
 if TestResult(!=0){
   echo "failed due to exit code 1"
   initiateMail()
 }
 
 def command(script) {
 if (isUnix()) {    
 return sh(returnStatus: true, script: script);} 
 else {     
 return bat(returnStatus: true, script: script);} }