Die Verwendung von Trap ist nicht immer eine Option. Wenn Sie beispielsweise eine wiederverwendbare Funktion schreiben, die eine Fehlerbehandlung erfordert und von jedem Skript aus aufgerufen werden kann (nachdem Sie die Datei mit Hilfsfunktionen bezogen haben), kann diese Funktion nichts über die Beendigungszeit des äußeren Skripts annehmen. Das macht die Verwendung von Fallen sehr schwierig. Ein weiterer Nachteil der Verwendung von Traps ist die schlechte Kompositionsfähigkeit, da Sie das Risiko eingehen, frühere Traps zu überschreiben, die möglicherweise früher in der Aufruferkette eingerichtet wurden.
Es gibt einen kleinen Trick, mit dem Fehler ohne Fallen richtig behandelt werden können. Wie Sie vielleicht bereits aus anderen Antworten wissen, set -e
funktioniert dies nicht in Befehlen, wenn Sie den ||
Operator danach verwenden, selbst wenn Sie sie in einer Subshell ausführen. zB würde das nicht funktionieren:
#!/bin/sh
# prints:
#
# --> outer
# --> inner
# ./so_1.sh: line 16: some_failed_command: command not found
# <-- inner
# <-- outer
set -e
outer() {
echo '--> outer'
(inner) || {
exit_code=$?
echo '--> cleanup'
return $exit_code
}
echo '<-- outer'
}
inner() {
set -e
echo '--> inner'
some_failed_command
echo '<-- inner'
}
outer
Der ||
Bediener muss jedoch verhindern, dass er vor der Bereinigung von der äußeren Funktion zurückkehrt. Der Trick besteht darin, den inneren Befehl im Hintergrund auszuführen und dann sofort darauf zu warten. Daswait
eingebaute System gibt den Exit-Code des inneren Befehls zurück, und jetzt verwenden Sie ||
after wait
und nicht die innere Funktion, sodass sie set -e
in letzterer ordnungsgemäß funktioniert:
#!/bin/sh
# prints:
#
# --> outer
# --> inner
# ./so_2.sh: line 27: some_failed_command: command not found
# --> cleanup
set -e
outer() {
echo '--> outer'
inner &
wait $! || {
exit_code=$?
echo '--> cleanup'
return $exit_code
}
echo '<-- outer'
}
inner() {
set -e
echo '--> inner'
some_failed_command
echo '<-- inner'
}
outer
Hier ist die generische Funktion, die auf dieser Idee aufbaut. Es sollte in allen POSIX-kompatiblen Shells funktionieren, wenn Sie es entfernenlocal
Schlüsselwörter , dh alle local x=y
durch nur ersetzen x=y
:
# [CLEANUP=cleanup_cmd] run cmd [args...]
#
# `cmd` and `args...` A command to run and its arguments.
#
# `cleanup_cmd` A command that is called after cmd has exited,
# and gets passed the same arguments as cmd. Additionally, the
# following environment variables are available to that command:
#
# - `RUN_CMD` contains the `cmd` that was passed to `run`;
# - `RUN_EXIT_CODE` contains the exit code of the command.
#
# If `cleanup_cmd` is set, `run` will return the exit code of that
# command. Otherwise, it will return the exit code of `cmd`.
#
run() {
local cmd="$1"; shift
local exit_code=0
local e_was_set=1; if ! is_shell_attribute_set e; then
set -e
e_was_set=0
fi
"$cmd" "$@" &
wait $! || {
exit_code=$?
}
if [ "$e_was_set" = 0 ] && is_shell_attribute_set e; then
set +e
fi
if [ -n "$CLEANUP" ]; then
RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "$@"
return $?
fi
return $exit_code
}
is_shell_attribute_set() { # attribute, like "x"
case "$-" in
*"$1"*) return 0 ;;
*) return 1 ;;
esac
}
Anwendungsbeispiel:
#!/bin/sh
set -e
# Source the file with the definition of `run` (previous code snippet).
# Alternatively, you may paste that code directly here and comment the next line.
. ./utils.sh
main() {
echo "--> main: $@"
CLEANUP=cleanup run inner "$@"
echo "<-- main"
}
inner() {
echo "--> inner: $@"
sleep 0.5; if [ "$1" = 'fail' ]; then
oh_my_god_look_at_this
fi
echo "<-- inner"
}
cleanup() {
echo "--> cleanup: $@"
echo " RUN_CMD = '$RUN_CMD'"
echo " RUN_EXIT_CODE = $RUN_EXIT_CODE"
sleep 0.3
echo '<-- cleanup'
return $RUN_EXIT_CODE
}
main "$@"
Beispiel ausführen:
$ ./so_3 fail; echo "exit code: $?"
--> main: fail
--> inner: fail
./so_3: line 15: oh_my_god_look_at_this: command not found
--> cleanup: fail
RUN_CMD = 'inner'
RUN_EXIT_CODE = 127
<-- cleanup
exit code: 127
$ ./so_3 pass; echo "exit code: $?"
--> main: pass
--> inner: pass
<-- inner
--> cleanup: pass
RUN_CMD = 'inner'
RUN_EXIT_CODE = 0
<-- cleanup
<-- main
exit code: 0
Das einzige, was Sie bei der Verwendung dieser Methode beachten müssen, ist, dass alle Änderungen der Shell-Variablen, die von dem Befehl, an den Sie übergeben, vorgenommen wurden run
, nicht an die aufrufende Funktion weitergegeben werden, da der Befehl in einer Subshell ausgeführt wird.