Bash中有TRY CATCH命令吗

我正在写一个shell脚本,需要检查终端应用程序是否已安装。我想使用TRY/CATCH命令来做到这一点,除非有更整洁的方法。

542141 次浏览

Bash中有TRY CATCH命令吗?

不。

Bash不像在许多编程语言中那样拥有那么多奢侈的东西。

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

使用# EYZ0:

如果command1失败,那么command2运行如下

command1 || command2

类似地,使用&&,如果command1成功,command2将运行

try/catch最接近的近似如下所示

{ # try


command1 &&
#save your output


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

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

set -e

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

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

bash不会在检测到错误状态时中止正在运行的执行(除非您设置了-e标志)。提供try/catch的编程语言这样做是为了让抑制摆脱这种特殊情况(因此通常称为“异常”)。

相反,在bash中,只有有问题的命令退出时退出码大于0,表示错误状态。当然,您可以检查它,但由于没有任何自动的救助,因此try / catch没有意义。它只是缺乏背景。

然而,你可以通过使用子shell来模拟救助,它可以在你决定的点终止:

(
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

而不是some_conditionif,你也可以尝试一个命令,如果它失败(退出代码大于0),保释:

(
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),而且不能使用像样的异常对象。

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

使用上面提到的-e标记到shell,你甚至可以剥离显式的exit语句:

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

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

根据我在这里找到的一些答案,我为自己的项目制作了一个小的帮助文件:

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
}

正如大家所说,bash没有适当的语言支持的try/catch语法。您可以使用-e参数启动bash,或者在脚本中使用set -e来中止整个bash进程,如果任何命令有非零退出码的话。(你也可以set +e来临时允许失败的命令。)

因此,模拟try/catch块的一种技术是在启用-e的情况下启动子进程来完成工作。然后在主进程中,检查子进程的返回码。

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

#!/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块,但它可能会为您带来类似的体验。

我已经开发了一个几乎完美无缺的尝试。Catch在bash中的实现,允许您编写如下代码:

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使用回溯和异常的错误处理(以及其他一些不错的功能)。

下面的代码只负责try &问题:

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

你可以使用trap:

# EYZ0

翻译:

(
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
)

有很多类似的解决方案可能会起作用。下面是一个简单有效的方法来完成try/catch,并在评论中进行了解释。

#!/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

我用了一个非常简单的方法:

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

你可以:

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

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

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

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

trycatchfinally命令的语法如下所示。

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

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

注意:为了简化脚本,删除了cdko选项。

catch命令的语法如下所示。

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

选项定义如下。第一个列表的值是状态。子值是消息。如果有更多的消息

. list,则其余消息将被忽略

-e表示[[ $value == "$string" ]](该值必须匹配列表中至少一个字符串)
-n表示[[ $value != "$string" ]](该值不能匹配列表中的任何字符串)
-o表示[[ $value != $pattern ]](该值不能匹配列表中的任何模式)
-p表示[[ $value == $pattern ]](该值必须匹配列表中的至少一个模式)
-r表示[[ $value =~ $regex ]](该值必须匹配列表中至少一个扩展正则表达式)
-t表示[[ ! $value =~ $regex ]](该值不能匹配列表中的任何扩展正则表达式)

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

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"
}

下面是一个示例,它假设上面的脚本存储在名为simple的文件中。makefifo文件包含了这个答案中描述的脚本。假设名为4444kkkkk的文件不存在,因此会发生异常。来自ls 4444kkkkk命令的错误消息输出被自动抑制,直到在适当的catch块中。

#!/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

另一个使用throw的例子可以通过用下面的脚本替换函数MyFunction3来创建。

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

下面是我在其他的答案中使用的简化脚本的完整副本。除了额外的错误检查之外,还有一个别名允许用户更改现有别名的名称。语法如下所示。如果省略了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))"
}

我可以用“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}"

假设终端应用名为'app'(你希望它在你的$PATH中):

if [[ ! `which app` ]]; then
# run code if app not installed
else
# run code if app is installed
fi

我使用的方法是这样的:

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

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

.输出说明

借用肖恩·梅奥的回答,你可以这样做

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

效果很好