In der typischen imperativen Programmierung schreiben Sie Befehlssequenzen und sie werden nacheinander mit explizitem Steuerungsfluss ausgeführt. Beispielsweise:
if [ -f file1 ]; then # If file1 exists ...
cp file1 file2 # ... create file2 as a copy of a file1
fi
etc.
Wie aus dem Beispiel hervorgeht, können Sie bei der imperativen Programmierung den Ablauf der Ausführung ganz einfach verfolgen, indem Sie sich immer von einer bestimmten Codezeile aus nach oben arbeiten, um den Ausführungskontext zu bestimmen Position im Flow (oder die Positionen der Anrufer, wenn Sie Funktionen schreiben).
Wie Rückrufe den Fluss verändern
Wenn Sie Rückrufe verwenden, geben Sie an, wann eine Anweisung aufgerufen werden soll, anstatt sie "geografisch" zu platzieren. Typische Beispiele in anderen Programmierumgebungen sind Fälle wie „Diese Ressource herunterladen und diesen Rückruf aufrufen, wenn der Download abgeschlossen ist“. Bash verfügt nicht über ein generisches Rückrufkonstrukt dieser Art, es verfügt jedoch über Rückrufe zur Fehlerbehandlung und in einigen anderen Situationen. Zum Beispiel (man muss zuerst die Befehlsersetzung und die Bash- Exit-Modi verstehen, um dieses Beispiel zu verstehen):
#!/bin/bash
scripttmp=$(mktemp -d) # Create a temporary directory (these will usually be created under /tmp or /var/tmp/)
cleanup() { # Declare a cleanup function
rm -rf "${scripttmp}" # ... which deletes the temporary directory we just created
}
trap cleanup EXIT # Ask Bash to call cleanup on exit
Wenn Sie dies selbst ausprobieren möchten, speichern Sie die obigen Informationen in einer Datei cleanUpOnExit.sh
, machen Sie sie beispielsweise ausführbar, und führen Sie sie aus:
chmod 755 cleanUpOnExit.sh
./cleanUpOnExit.sh
Mein Code hier ruft die cleanup
Funktion niemals explizit auf . Es teilt Bash mit, wann er es aufrufen soll, z trap cleanup EXIT
. B. "Lieber Bash, führe den cleanup
Befehl beim Beenden aus" (und ist cleanup
zufällig eine Funktion, die ich zuvor definiert habe, aber es kann alles sein, was Bash versteht). Bash unterstützt dies für alle nicht schwerwiegenden Signale, Exits, Befehlsfehler und das allgemeine Debuggen (Sie können einen Rückruf angeben, der vor jedem Befehl ausgeführt wird). Der Rückruf ist hier die cleanup
Funktion, die von Bash direkt vor dem Beenden der Shell "zurückgerufen" wird.
Sie können die Fähigkeit von Bash verwenden, Shell-Parameter als Befehle auszuwerten, um ein Callback-orientiertes Framework zu erstellen. Das würde den Rahmen dieser Antwort sprengen und vielleicht mehr Verwirrung stiften, wenn man annimmt, dass das Weitergeben von Funktionen immer Rückrufe beinhaltet. Unter Bash: Übergeben einer Funktion als Parameter finden Sie einige Beispiele für die zugrunde liegende Funktionalität. Die Idee hierbei ist, wie bei Rückrufen zur Ereignisbehandlung, dass Funktionen Daten als Parameter, aber auch andere Funktionen verwenden können. Dadurch können Anrufer sowohl Verhalten als auch Daten bereitstellen. Ein einfaches Beispiel für diesen Ansatz könnte so aussehen
#!/bin/bash
doonall() {
command="$1"
shift
for arg; do
"${command}" "${arg}"
done
}
backup() {
mkdir -p ~/backup
cp "$1" ~/backup
}
doonall backup "$@"
(Ich weiß, das ist ein bisschen nutzlos, da cp
es mit mehreren Dateien umgehen kann, es ist nur zur Veranschaulichung.)
Hier erstellen wir eine Funktion, doonall
die einen anderen Befehl als Parameter verwendet und auf die restlichen Parameter anwendet. dann verwenden wir das, um die backup
Funktion für alle Parameter aufzurufen, die dem Skript gegeben wurden. Das Ergebnis ist ein Skript, das alle Argumente nacheinander in ein Sicherungsverzeichnis kopiert.
Mit dieser Art von Ansatz können Funktionen mit einer einzigen Verantwortung geschrieben werden: doonall
Die Verantwortung der Funktion besteht darin, nacheinander alle Argumente auszuführen. backup
Es liegt in der Verantwortung des Benutzers, eine Kopie seines (einzigen) Arguments in einem Sicherungsverzeichnis zu erstellen. Beides doonall
und backup
kann in anderen Kontexten verwendet werden, was mehr Wiederverwendung von Code, bessere Tests usw. ermöglicht.
In diesem Fall ist der Rückruf die backup
Funktion, die wir doonall
für jedes ihrer anderen Argumente "zurückrufen" sollen - wir liefern das doonall
Verhalten (das erste Argument) sowie die Daten (die verbleibenden Argumente).
(Beachten Sie, dass ich in dem im zweiten Beispiel gezeigten Anwendungsfall den Begriff „Rückruf“ nicht selbst verwenden würde, aber dies ist möglicherweise eine Gewohnheit, die sich aus den von mir verwendeten Sprachen ergibt. Ich betrachte dies als Weitergabe von Funktionen oder Lambdas anstatt Rückrufe in einem ereignisorientierten System zu registrieren.)