Das Ausführen exit
in einer Subshell ist eine Falle:
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
Das Skript druckt 42, beendet die Subshell mit dem Rückkehrcode 1
und fährt mit dem Skript fort. Selbst das Ersetzen des Anrufs durch echo $(CALC) || exit 1
hilft nicht, da der Rückgabecode echo
0 ist, unabhängig vom Rückgabecode von calc
. Und calc
wird vor ausgeführt echo
.
Noch rätselhafter ist es, den Effekt zu vereiteln, exit
indem Sie local
ihn wie im folgenden Skript in Built- in einschließen. Ich bin über das Problem gestolpert, als ich eine Funktion zur Überprüfung eines Eingabewerts geschrieben habe. Beispiel:
Ich möchte eine Datei mit dem Namen "year month day.log" 20141211.log
für heute erstellen . Das Datum wird von einem Benutzer eingegeben, der möglicherweise keinen angemessenen Wert angibt. Daher fname
überprüfe ich in meiner Funktion den Rückgabewert von date
, um die Gültigkeit der Benutzereingabe zu überprüfen:
#!/bin/bash
doit ()
{
local FNAME=$(fname "$1") || exit 1
touch "${FNAME}"
}
fname ()
{
date +"%Y%m%d.log" -d"$1" 2>/dev/null
if [ "$?" != 0 ] ; then
echo "fname reports \"Illegal Date\"" >&2
exit 1
fi
}
doit "$1"
Sieht gut aus. Lassen Sie das Skript benannt werden s.sh
. Wenn der Benutzer das Skript mit aufruft ./s.sh "Thu Dec 11 20:45:49 CET 2014"
, wird die Datei 20141211.log
erstellt. Wenn der Benutzer jedoch Folgendes eingibt ./s.sh "Thu hec 11 20:45:49 CET 2014"
, gibt das Skript Folgendes aus:
fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory
Die Zeile fname…
besagt, dass in der Subshell fehlerhafte Eingabedaten erkannt wurden. Das exit 1
am Ende der local …
Zeile wird jedoch nie ausgelöst, da die local
Direktive immer zurückkehrt 0
. Dies liegt daran local
, dass nach ausgeführt wird $(fname)
und somit seinen Rückkehrcode überschreibt. Aus diesem Grund wird das Skript fortgesetzt und touch
mit einem leeren Parameter aufgerufen. Dieses Beispiel ist einfach, aber das Verhalten von bash kann in einer realen Anwendung ziemlich verwirrend sein. Ich weiß, echte Programmierer benutzen keine Einheimischen. «
Um es deutlich zu machen: Ohne local
wird das Skript wie erwartet abgebrochen, wenn ein ungültiges Datum eingegeben wird.
Das Update ist, die Linie wie zu teilen
local FNAME
FNAME=$(fname "$1") || exit 1
Das seltsame Verhalten entspricht der Dokumentation local
in der Manpage von bash: "Der Rückgabestatus ist 0, sofern local nicht außerhalb einer Funktion verwendet wird, ein ungültiger Name angegeben wird oder name eine schreibgeschützte Variable ist."
Obwohl dies kein Fehler ist, habe ich das Gefühl, dass das Verhalten von bash nicht intuitiv ist. Mir ist die Ausführungsreihenfolge bekannt, local
sollte aber eine fehlerhafte Zuordnung nicht maskieren.
Meine erste Antwort enthielt einige Ungenauigkeiten. Nach einer aufschlussreichen und eingehenden Diskussion mit mikeserv (danke dafür) habe ich sie repariert.