Ci-dessous, un exemple de script implémenté try/catch/finally
dans bash.
Comme les autres réponses à cette question, les exceptions doivent être interceptées après avoir quitté un sous-processus.
Les exemples de scripts commencent par créer un fifo anonyme, qui est utilisé pour transmettre des messages de chaîne à partir d'un command exception
ou throw
à la fin du try
bloc le plus proche . Ici, les messages sont supprimés du fifo et placés dans une variable de tableau. Le statut est renvoyé à travers return
et exit
commandes et placé dans une autre variable. Pour entrer dans un catch
bloc, ce statut ne doit pas être nul. Les autres exigences pour entrer dans un catch
bloc sont transmises en tant que paramètres. Si la fin d'un catch
bloc est atteinte, l'état est mis à zéro. Si la fin du finally
bloc est atteinte et que l'état est toujours différent de zéro, un lancer implicite contenant les messages et l'état est exécuté. Le script nécessite l'appel de la fonction trycatchfinally
qui contient un gestionnaire d'exceptions non géré.
La syntaxe de la trycatchfinally
commande est donnée ci-dessous.
trycatchfinally [-cde] [-h ERR_handler] [-k] [-o debug_file] [-u unhandled_handler] [-v variable] fifo function
L' -c
option ajoute la pile d'appels aux messages d'exception.
L' -d
option active la sortie de débogage.
L' -e
option active les exceptions de commande.
L' -h
option permet à l'utilisateur de remplacer son propre gestionnaire d'exceptions de commande.
L' -k
option ajoute la pile d'appels à la sortie de débogage.
L' -o
option remplace le fichier de sortie par défaut qui est /dev/fd/2
.
L' -u
option permet à l'utilisateur de remplacer son propre gestionnaire d'exceptions non géré.
L' -v
option permet à l'utilisateur de renvoyer des valeurs via l'utilisation de la substitution de commande.
Le fifo
est le nom du fichier fifo.
La fonction function
est appelée par en trycatchfinally
tant que sous-processus.
Remarque: Les cdko
options ont été supprimées pour simplifier le script.
La syntaxe de la catch
commande est donnée ci-dessous.
catch [[-enoprt] list ...] ...
Les options sont définies ci-dessous. La valeur de la première liste est le statut. Les valeurs subséquentes sont les messages. S'il y a plus de messages que de listes, les messages restants sont ignorés.
-e
signifie [[ $value == "$string" ]]
(la valeur doit correspondre à au moins une chaîne de la liste)
-n
signifie [[ $value != "$string" ]]
(la valeur ne peut correspondre à aucune des chaînes de la liste)
-o
signifie [[ $value != $pattern ]]
(la valeur ne peut correspondre à aucun des modèles de la liste)
-p
signifie [[ $value == $pattern ]]
(la valeur a pour correspondre à au moins un modèle dans la liste)
-r
signifie [[ $value =~ $regex ]]
(la valeur doit correspondre à au moins une expression régulière étendue dans la liste)
-t
signifie [[ ! $value =~ $regex ]]
(la valeur ne peut correspondre à aucune des expressions régulières étendues dans la liste)
Le try/catch/finally
script est donné ci-dessous. Pour simplifier le script de cette réponse, la plupart des vérifications d'erreurs ont été supprimées. Cela a réduit la taille de 64%. Une copie complète de ce script peut être trouvée à mon autre réponse .
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"
}
Voici un exemple, qui suppose que le script ci-dessus est stocké dans le fichier nommé simple
. Le makefifo
fichier contient le script décrit dans cette réponse . L'hypothèse est faite que le fichier nommé 4444kkkkk
n'existe pas, provoquant ainsi une exception. La sortie du message d'erreur de la ls 4444kkkkk
commande est automatiquement supprimée jusqu'à l'intérieur du catch
bloc approprié .
#!/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-
Le script ci-dessus a été testé à l'aide de GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
. Le résultat de l'exécution de ce script est illustré ci-dessous.
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
Un autre exemple qui utilise un throw
peut être créé en remplaçant la fonction MyFunction3
par le script illustré ci-dessous.
MyFunction3() {
echo "entered MyFunction3" >&4
echo "This is from MyFunction3"
throw "3" "Orginal Status: 3" "Exception Type: throw"
echo "leaving MyFunction3" >&4
}
La syntaxe de la throw
commande est donnée ci-dessous. Si aucun paramètre n'est présent, l'état et les messages stockés dans les variables sont utilisés à la place.
throw [status] [message ...]
Le résultat de l'exécution du script modifié est illustré ci-dessous.
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