Als ich ein paar Mal über das Programmieren las, stieß ich auf das "Rückruf" -Konzept.
Komischerweise habe ich nie eine Erklärung gefunden, die ich für diesen Begriff "Rückruffunktion" als "didaktisch" oder "klar" bezeichnen kann (fast jede Erklärung, die ich las, schien sich von der anderen zu unterscheiden und ich fühlte mich verwirrt).
Existiert das "Rückruf" -Konzept der Programmierung in Bash? Wenn ja, antworten Sie bitte mit einem kleinen, einfachen Bash-Beispiel.
declarative.bash
als Framework interessant, das Funktionen explizit nutzt, die so konfiguriert sind, dass sie aufgerufen werden, wenn ein bestimmter Wert benötigt wird.Antworten:
In der typischen imperativen Programmierung schreiben Sie Befehlssequenzen und sie werden nacheinander mit explizitem Steuerungsfluss ausgeführt. Beispielsweise:
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):
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:Mein Code hier ruft die
cleanup
Funktion niemals explizit auf . Es teilt Bash mit, wann er es aufrufen soll, ztrap cleanup EXIT
. B. "Lieber Bash, führe dencleanup
Befehl beim Beenden aus" (und istcleanup
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 diecleanup
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
(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 diebackup
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. Beidesdoonall
undbackup
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 wirdoonall
für jedes ihrer anderen Argumente "zurückrufen" sollen - wir liefern dasdoonall
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.)
quelle
Zunächst ist zu beachten, dass eine Funktion durch ihre Verwendung und nicht durch ihre Funktion als Rückruffunktion definiert wird. Ein Rückruf ist, wenn Code, den Sie schreiben, von Code aufgerufen wird, den Sie nicht geschrieben haben. Sie bitten das System, Sie zurückzurufen, wenn ein bestimmtes Ereignis eintritt.
Ein Beispiel für einen Rückruf bei der Shell-Programmierung sind Traps. Eine Falle ist ein Rückruf, der nicht als Funktion, sondern als auszuwertender Code ausgedrückt wird. Sie fordern die Shell auf, Ihren Code aufzurufen, wenn die Shell ein bestimmtes Signal empfängt.
Ein weiteres Beispiel für einen Rückruf ist die
-exec
Aktion desfind
Befehls. Die Aufgabe desfind
Befehls besteht darin, Verzeichnisse rekursiv zu durchlaufen und jede Datei nacheinander zu verarbeiten. Standardmäßig wird bei der Verarbeitung der Dateiname (implizit-print
) gedruckt , bei-exec
der Verarbeitung wird jedoch ein von Ihnen angegebener Befehl ausgeführt. Dies passt zur Definition eines Rückrufs, obwohl dieser bei Rückrufen nicht sehr flexibel ist, da der Rückruf in einem separaten Prozess ausgeführt wird.Wenn Sie eine Suchfunktion implementiert haben, können Sie festlegen, dass jede Datei mit einer Rückruffunktion aufgerufen wird. Hier ist eine ultra-vereinfachte find-like-Funktion, die einen Funktionsnamen (oder einen externen Befehlsnamen) als Argument verwendet und ihn für alle regulären Dateien im aktuellen Verzeichnis und seinen Unterverzeichnissen aufruft. Die Funktion wird als Rückruf verwendet, der jedes Mal aufgerufen wird, wenn
call_on_regular_files
eine reguläre Datei gefunden wird.Rückrufe sind in der Shell-Programmierung nicht so häufig wie in einigen anderen Umgebungen, da Shells hauptsächlich für einfache Programme entwickelt wurden. Rückrufe sind in Umgebungen häufiger, in denen Daten- und Steuerungsflüsse mit größerer Wahrscheinlichkeit zwischen Teilen des Codes hin und her wandern, die unabhängig voneinander geschrieben und verteilt werden: dem Basissystem, verschiedenen Bibliotheken und dem Anwendungscode.
quelle
foreach_server() { declare callback="$1"; declare server; for server in 192.168.0.1 192.168.0.2 192.168.0.3; do "$callback" "$server"; done; }
was Sie laufen könnenforeach_server echo
,foreach_server nslookup
usw. Dasdeclare callback="$1"
ist ungefähr so einfach wie es bekommen kann wenn: der Rückruf in irgendwo geführt werden muss, oder es ist kein Rückruf."Rückrufe" sind nur Funktionen, die als Argumente an andere Funktionen übergeben werden.
Auf Shell-Ebene bedeutet dies einfach, dass Skripte / Funktionen / Befehle als Argumente an andere Skripte / Funktionen / Befehle übergeben werden.
Betrachten Sie als einfaches Beispiel das folgende Skript:
mit der Synopse
wird
filter
auf jedesfile
Argument angewendet und danncommand
mit den Ausgaben der Filter als Argumente aufgerufen .Zum Beispiel:
Das kommt dem sehr nahe, was man in Lisp machen kann (nur ein Scherz ;-))
Einige Leute bestehen darauf, den "Rückruf" -Begriff auf "Ereignishandler" und / oder "Closure" (Funktion + Daten / Umgebungstupel) zu beschränken. Dies ist keineswegs die allgemein akzeptierte Bedeutung. Und ein Grund, warum "Rückrufe" im engeren Sinne in der Shell nicht viel Sinn machen, ist, dass Pipes + Parallelität + dynamische Programmierfähigkeiten so viel leistungsfähiger sind und Sie bereits für sie in Bezug auf die Leistung zahlen, selbst wenn Sie versuchen Sie, die Shell als eine klobige Version von
perl
oder zu verwendenpython
.quelle
%
Interpolation in Filtern, die ganze Sache könnte reduziert:cmd=$1; shift; flt=$1; shift; $cmd <($flt "$1") <($flt "$2")
. Aber das ist viel weniger nützlich und illustrativ imho.$1 <($2 "$3") <($2 "$4")
So'ne Art.
Eine einfache Möglichkeit, einen Rückruf in bash zu implementieren, besteht darin, den Namen eines Programms als Parameter zu akzeptieren, der als "Rückruffunktion" fungiert.
Dies würde wie folgt verwendet werden:
Natürlich haben Sie keine Schließungen in der Bash. Daher hat die Rückruffunktion keinen Zugriff auf die Variablen auf der Anruferseite. Sie können jedoch Daten, die der Rückruf benötigt, in Umgebungsvariablen speichern. Das Zurückgeben von Informationen vom Rückruf an das Aufruferskript ist schwieriger. Daten können in eine Datei eingefügt werden.
Wenn Ihr Design zulässt, dass alles in einem einzigen Prozess behandelt wird, können Sie eine Shell-Funktion für den Rückruf verwenden, und in diesem Fall hat die Rückruffunktion natürlich Zugriff auf die Variablen auf der Aufruferseite.
quelle
Nur um ein paar Worte zu den anderen Antworten hinzuzufügen. Der Funktionsrückruf wird für Funktionen ausgeführt, die außerhalb der zurückrufenden Funktion liegen. Damit dies möglich ist, muss entweder eine vollständige Definition der zurückzurufenden Funktion an die zurückrufende Funktion übergeben werden, oder ihr Code sollte für die zurückrufende Funktion verfügbar sein.
Ersteres (Weitergeben von Code an eine andere Funktion) ist möglich, obwohl ich ein Beispiel überspringe, das Komplexität mit sich bringen würde. Letzteres (Übergeben der Funktion als Name) ist eine gängige Praxis, da die Variablen und Funktionen, die außerhalb des Gültigkeitsbereichs einer Funktion deklariert wurden, in dieser Funktion verfügbar sind, solange ihre Definition dem Aufruf der Funktion vorausgeht, die sie ausführt (was wiederum der Fall ist) , wie zu deklarieren, bevor es heißt).
Beachten Sie auch, dass beim Exportieren von Funktionen ein ähnliches Problem auftritt. Eine Shell, die eine Funktion importiert, verfügt möglicherweise über ein Framework, das darauf wartet, dass Funktionsdefinitionen sie in Aktion setzen. Funktionsexport ist in Bash vorhanden und verursachte zuvor schwerwiegende Probleme, übrigens (das wurde Shellshock genannt):
Ich vervollständige diese Antwort mit einer weiteren Methode zum Übergeben einer Funktion an eine andere Funktion, die in Bash nicht explizit vorhanden ist. Dieser gibt es nach Adresse, nicht nach Namen. Dies kann zum Beispiel in Perl gefunden werden. Bash bietet auf diese Weise weder Funktionen noch Variablen an. Wenn Sie jedoch ein breiteres Bild mit Bash als Beispiel haben möchten, sollten Sie wissen, dass sich der Funktionscode möglicherweise irgendwo im Speicher befindet und dass auf diesen Code von diesem Speicherort aus zugegriffen werden kann nannte seine Adresse.
quelle
Eines der einfachsten Beispiele für Rückrufe in Bash ist eines, mit dem viele Leute vertraut sind, aber nicht wissen, welches Entwurfsmuster sie tatsächlich verwenden:
cron
Mit Cron können Sie eine ausführbare Datei (eine Binärdatei oder ein Skript) angeben, die bzw. das das Cron-Programm zurückruft, wenn bestimmte Bedingungen erfüllt sind (die Zeitangabe).
Angenommen, Sie haben ein Skript namens
doEveryDay.sh
. Das Skript kann ohne Rückruf wie folgt geschrieben werden:Der Callback-Weg, es zu schreiben, ist einfach:
Dann würden Sie in crontab so etwas einstellen
Sie müssten dann nicht den Code schreiben, um auf die Auslösung des Ereignisses zu warten, sondern müssen sich darauf verlassen
cron
, dass Sie Ihren Code zurückrufen.Betrachten Sie nun, wie Sie diesen Code in Bash schreiben würden.
Wie würden Sie ein anderes Skript / eine andere Funktion in Bash ausführen?
Schreiben wir eine Funktion:
Jetzt haben Sie eine Funktion erstellt, die einen Rückruf akzeptiert. Sie können es einfach so nennen:
Natürlich kehrt die Funktion alle 24 Stunden nie zurück. Bash ist insofern einzigartig, als wir es sehr einfach asynchron machen und einen Prozess erzeugen können, indem wir Folgendes anhängen
&
:Wenn Sie dies nicht als Funktion möchten, können Sie dies stattdessen als Skript ausführen:
Wie Sie sehen, sind Rückrufe in bash trivial. Es ist einfach:
Und den Rückruf anzurufen ist einfach:
Wie Sie oben sehen können, sind Rückrufe selten direkte Merkmale von Sprachen. Sie programmieren in der Regel kreativ mit vorhandenen Sprachfunktionen. Jede Sprache, die einen Zeiger / eine Referenz / eine Kopie eines Codeblocks / einer Funktion / eines Skripts speichern kann, kann Rückrufe implementieren.
quelle
watch
undfind
(wenn verwendet mit-exec
Parameter)Ein Rückruf ist eine Funktion, die aufgerufen wird, wenn ein Ereignis eintritt. Mit
bash
ist das einzige Event Handling Mechanismus zu Signalen im Zusammenhang, die Shell verlassen, und erweitert , um Fehler Ereignisse Shell, Debug - Ereignisse und Funktion / sourced Skripte zurückgeben Ereignisse.Hier ist ein Beispiel für einen nutzlosen, aber einfachen Rückruf, der Signalfallen nutzt.
Erstellen Sie zuerst das Skript, das den Rückruf implementiert:
Führen Sie dann das Skript in einem Terminal aus:
$ ./callback-example
und bei einem anderen senden Sie das
USR1
Signal an den Shell-Prozess.Jedes gesendete Signal sollte die Anzeige von Zeilen wie diesen im ersten Terminal auslösen:
ksh93
bietet als Shell, die viele Funktionen implementiert, diebash
später übernommen wurden, das, was sie "Disziplinfunktionen" nennt. Diese Funktionen, die mit nicht verfügbar sindbash
, werden aufgerufen, wenn eine Shell-Variable geändert oder referenziert (dh gelesen) wird. Dies eröffnet den Weg für interessantere ereignisgesteuerte Anwendungen.Diese Funktion ermöglichte es beispielsweise, Rückrufe im X11 / Xt / Motif-Stil auf Grafik-Widgets in einer alten Version der
ksh
genannten Grafikerweiterungen zu implementierendtksh
. Siehe dksh-Handbuch .quelle