Poniżej znajduje się przykład skryptu, który implementuje się try/catch/finally
w bash.
Podobnie jak inne odpowiedzi na to pytanie, wychodząc z podprocesu należy wychwycić wyjątki.
Przykładowe skrypty zacząć od tworzenia anonimowej FIFO, który jest używany do przekazywania wiadomości ciąg przez command exception
lub throw
do końca najbliższego try
bloku. Tutaj wiadomości są usuwane z fifo i umieszczane w zmiennej tablicowej. Status jest zwracany przez return
i exit
poleceń i umieszczone w innej zmiennej. Aby wprowadzić catch
blok, ten status nie może wynosić zero. Inne wymagania do wprowadzenia catch
bloku są przekazywane jako parametry. Po osiągnięciu końca catch
bloku status jest ustawiany na zero. Jeśli osiągnięty zostanie koniec finally
bloku, a status nadal jest niezerowy, wykonywany jest dorozumiany rzut zawierający komunikaty i status. Skrypt wymaga wywołania funkcji trycatchfinally
zawierającej nieobsługiwany moduł obsługi wyjątków.
Składnia trycatchfinally
polecenia jest podana poniżej.
trycatchfinally [-cde] [-h ERR_handler] [-k] [-o debug_file] [-u unhandled_handler] [-v variable] fifo function
-c
Opcja dodaje stos wywołań do wiadomości wyjątków. Opcja umożliwia wyjście debugowania.
Ta opcja włącza wyjątki poleceń.
Ta opcja pozwala użytkownikowi zastąpić własny moduł obsługi wyjątków poleceń. Opcja dodaje stos wywołań do wyjścia debugowania. Opcja zastępuje domyślny plik wyjściowy, który jest .
Ta opcja pozwala użytkownikowi zastąpić własną nieobsługiwaną procedurę obsługi wyjątków.
Ta opcja pozwala użytkownikowi na przekazywanie wartości przez użycie polecenia Podstawienie.
Jest to nazwa pliku fifo.
Funkcja jest wywoływana przez jako podproces.
-d
-e
-h
-k
-o
/dev/fd/2
-u
-v
fifo
function
trycatchfinally
Uwaga: cdko
Opcje zostały usunięte, aby uprościć skrypt.
Składnia catch
polecenia jest podana poniżej.
catch [[-enoprt] list ...] ...
Opcje są zdefiniowane poniżej. Wartością pierwszej listy jest status. Kolejne wartości to komunikaty. Jeśli wiadomości jest więcej niż listy, pozostałe wiadomości są ignorowane.
-e
oznacza [[ $value == "$string" ]]
(wartość musi odpowiadać co najmniej jednemu ciągowi na liście)
-n
oznacza [[ $value != "$string" ]]
(wartość nie może pasować do żadnego z ciągów na liście)
-o
oznacza [[ $value != $pattern ]]
(wartość nie może pasować do żadnego z wzorców na liście)
-p
oznacza [[ $value == $pattern ]]
(wartość ma dopasowanie co najmniej jednego wzorca na liście)
-r
oznacza [[ $value =~ $regex ]]
(wartość musi pasować do co najmniej jednego rozszerzonego wyrażenia regularnego na liście)
-t
oznacza [[ ! $value =~ $regex ]]
(wartość nie może pasować do żadnego z rozszerzonych wyrażeń regularnych na liście)
try/catch/finally
Skrypt znajduje się poniżej. Aby uprościć skrypt dla tej odpowiedzi, większość kontroli błędów została usunięta. To zmniejszyło rozmiar o 64%. Kompletną kopię tego skryptu można znaleźć w mojej drugiej odpowiedzi .
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"
}
Poniżej znajduje się przykład, który zakłada, że powyższy skrypt jest przechowywany w pliku o nazwie simple
. makefifo
Plik zawiera skrypt opisany w tej odpowiedzi . Przyjęto założenie, że plik o podanej nazwie 4444kkkkk
nie istnieje, co powoduje wystąpienie wyjątku. Wyjście komunikatu błędu z ls 4444kkkkk
polecenia jest automatycznie pomijane aż do odpowiedniego catch
bloku.
#!/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-
Powyższy skrypt został przetestowany przy użyciu GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
. Wynik działania tego skryptu pokazano poniżej.
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
Kolejny przykład, w którym zastosowano a, throw
można utworzyć, zastępując funkcję MyFunction3
skryptem pokazanym poniżej.
MyFunction3() {
echo "entered MyFunction3" >&4
echo "This is from MyFunction3"
throw "3" "Orginal Status: 3" "Exception Type: throw"
echo "leaving MyFunction3" >&4
}
Składnia throw
polecenia jest podana poniżej. Jeśli nie ma żadnych parametrów, zamiast tego używane są status i komunikaty zapisane w zmiennych.
throw [status] [message ...]
Wynik działania zmodyfikowanego skryptu pokazano poniżej.
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