Unten finden Sie ein Beispiel für ein Skript, das try/catch/finallyin bash implementiert wird .
Wie bei anderen Antworten auf diese Frage müssen Ausnahmen nach dem Beenden eines Unterprozesses abgefangen werden.
Die Beispielskripte beginnen mit der Erstellung eines anonymen Fifos, mit dem Zeichenfolgennachrichten von einem command exceptionoder throwzum Ende des nächsten tryBlocks übergeben werden. Hier werden die Nachrichten aus dem FIFO entfernt und in eine Array-Variable eingefügt. Der Status wird über returnund exitBefehle zurückgegeben und in eine andere Variable gestellt. Um einen catchBlock einzugeben , darf dieser Status nicht Null sein. Andere Anforderungen zur Eingabe eines catchBlocks werden als Parameter übergeben. Wenn das Ende eines catchBlocks erreicht ist, wird der Status auf Null gesetzt. Wenn das Ende des finallyBlocks erreicht ist und der Status immer noch ungleich Null ist, wird ein impliziter Wurf ausgeführt, der die Nachrichten und den Status enthält. Das Skript erfordert den Aufruf der Funktion, trycatchfinallydie einen nicht behandelten Ausnahmebehandler enthält.
Die Syntax für den trycatchfinallyBefehl ist unten angegeben.
trycatchfinally [-cde] [-h ERR_handler] [-k] [-o debug_file] [-u unhandled_handler] [-v variable] fifo function
Die -cOption fügt den Aufrufstapel zu den Ausnahmemeldungen hinzu.
Die -dOption aktiviert die Debug-Ausgabe.
Die -eOption aktiviert Befehlsausnahmen.
Mit dieser -hOption kann der Benutzer seinen eigenen Befehlsausnahmehandler ersetzen.
Die -kOption fügt den Aufrufstapel der Debug-Ausgabe hinzu.
Die -oOption ersetzt die Standardausgabedatei /dev/fd/2.
Mit dieser -uOption kann der Benutzer seinen eigenen nicht behandelten Ausnahmebehandler ersetzen.
Mit dieser -vOption kann der Benutzer Werte mithilfe der Befehlssubstitution zurückgeben.
Das fifoist der Fifo-Dateiname.
Die Funktion functionwird von trycatchfinallyals Unterprozess aufgerufen .
Hinweis: Die cdkoOptionen wurden entfernt, um das Skript zu vereinfachen.
Die Syntax für den catchBefehl ist unten angegeben.
catch [[-enoprt] list ...] ...
Die Optionen sind unten definiert. Der Wert für die erste Liste ist der Status. Nachfolgende Werte sind die Nachrichten. Wenn mehr Nachrichten als Listen vorhanden sind, werden die verbleibenden Nachrichten ignoriert.
-ebedeutet [[ $value == "$string" ]](der Wert muss mit mindestens einer Zeichenfolge in der Liste übereinstimmen)
-nbedeutet [[ $value != "$string" ]](der Wert kann mit keiner der Zeichenfolgen in der Liste übereinstimmen )
-obedeutet [[ $value != $pattern ]](der Wert kann mit keinem der Muster in der Liste übereinstimmen)
-pbedeutet [[ $value == $pattern ]](der Wert hat mit mindestens einem Muster in der Liste übereinstimmen)
-rbedeutet [[ $value =~ $regex ]](der Wert muss mit mindestens einem erweiterten regulären Ausdruck in der Liste übereinstimmen )
-tbedeutet [[ ! $value =~ $regex ]](der Wert kann mit keinem der erweiterten regulären Ausdrücke in der Liste übereinstimmen)
Das try/catch/finallySkript ist unten angegeben. Um das Skript für diese Antwort zu vereinfachen, wurde der größte Teil der Fehlerprüfung entfernt. Dies reduzierte die Größe um 64%. Eine vollständige Kopie dieses Skripts finden Sie unter meiner anderen Antwort .
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"
}
Unten sehen Sie ein Beispiel, bei dem davon ausgegangen wird, dass das obige Skript in der genannten Datei gespeichert ist simple. Die makefifoDatei enthält das in dieser Antwort beschriebene Skript . Es wird davon ausgegangen, dass die genannte Datei 4444kkkkknicht vorhanden ist und daher eine Ausnahme auftritt. Die vom ls 4444kkkkkBefehl ausgegebene Fehlermeldung wird automatisch unterdrückt, bis sie sich im entsprechenden catchBlock befindet.
#!/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-
Das obige Skript wurde mit getestet GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17). Die Ausgabe beim Ausführen dieses Skripts ist unten dargestellt.
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
Ein weiteres Beispiel, das a verwendet, throwkann erstellt werden, indem die Funktion MyFunction3durch das unten gezeigte Skript ersetzt wird.
MyFunction3() {
echo "entered MyFunction3" >&4
echo "This is from MyFunction3"
throw "3" "Orginal Status: 3" "Exception Type: throw"
echo "leaving MyFunction3" >&4
}
Die Syntax für den throwBefehl ist unten angegeben. Wenn keine Parameter vorhanden sind, werden stattdessen Status und Nachrichten verwendet, die in den Variablen gespeichert sind.
throw [status] [message ...]
Die Ausgabe der Ausführung des geänderten Skripts ist unten dargestellt.
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