Quelques fois, lorsque j'ai lu sur la programmation, je suis tombé sur le concept de "rappel".
Curieusement, je n'ai jamais trouvé d'explication que je puisse appeler «didactique» ou «claire» pour ce terme «fonction de rappel» (presque toutes les explications que j'ai lues me semblaient assez différentes les unes des autres et je me sentais confuse).
Le concept de "rappel" de la programmation existe-t-il dans Bash? Dans l'affirmative, veuillez répondre avec un petit exemple simple de Bash.
declarative.bash
intéressant, comme un cadre qui exploite explicitement les fonctions configurées pour être invoquées lorsqu'une valeur donnée est nécessaire.Réponses:
Dans la programmation impérative typique , vous écrivez des séquences d'instructions et elles sont exécutées les unes après les autres, avec un flux de contrôle explicite. Par exemple:
etc.
Comme le montre l'exemple, dans la programmation impérative, vous suivez assez facilement le flux d'exécution, en remontant toujours à partir d'une ligne de code donnée pour déterminer son contexte d'exécution, sachant que toutes les instructions que vous donnerez seront exécutées à la suite de leur l'emplacement dans le flux (ou l'emplacement de leurs sites d'appel, si vous écrivez des fonctions).
Comment les rappels modifient le flux
Lorsque vous utilisez des rappels, au lieu de placer l'utilisation d'un ensemble d'instructions «géographiquement», vous décrivez quand il doit être appelé. Des exemples typiques dans d'autres environnements de programmation sont des cas tels que «téléchargez cette ressource, et lorsque le téléchargement est terminé, appelez ce rappel». Bash n'a pas de construction de rappel générique de ce type, mais il a des rappels, pour la gestion des erreurs et quelques autres situations; par exemple (il faut d'abord comprendre les modes de substitution de commandes et de sortie Bash pour comprendre cet exemple):
Si vous voulez essayer vous-même, enregistrez ce qui précède dans un fichier, par exemple
cleanUpOnExit.sh
, rendez-le exécutable et exécutez-le:Mon code ici n'appelle jamais explicitement la
cleanup
fonction; il indique à Bash quand l'appeler, en utilisanttrap cleanup EXIT
, par exemple «cher Bash, veuillez exécuter lacleanup
commande lorsque vous quittez» (etcleanup
il se trouve que c'est une fonction que j'ai définie plus tôt, mais cela pourrait être tout ce que Bash comprend). Bash le prend en charge pour tous les signaux non fatals, les sorties, les échecs de commande et le débogage général (vous pouvez spécifier un rappel qui est exécuté avant chaque commande). Le rappel ici est lacleanup
fonction, qui est «rappelée» par Bash juste avant la sortie du shell.Vous pouvez utiliser la capacité de Bash pour évaluer les paramètres du shell en tant que commandes, pour construire un framework orienté callback; c'est un peu au-delà de la portée de cette réponse, et provoquerait peut-être plus de confusion en suggérant que la transmission de fonctions implique toujours des rappels. Voir Bash: passer une fonction comme paramètre pour quelques exemples de la fonctionnalité sous-jacente. L'idée ici, comme pour les rappels de gestion d'événements, est que les fonctions peuvent prendre des données en tant que paramètres, mais aussi d'autres fonctions - cela permet aux appelants de fournir un comportement ainsi que des données. Un exemple simple de cette approche pourrait ressembler à
(Je sais que c'est un peu inutile car
cp
peut traiter plusieurs fichiers, c'est uniquement à titre d'illustration.)Ici, nous créons une fonction,
doonall
qui prend une autre commande, donnée en paramètre, et l'applique au reste de ses paramètres; nous utilisons ensuite cela pour appeler labackup
fonction sur tous les paramètres donnés au script. Le résultat est un script qui copie tous ses arguments, un par un, dans un répertoire de sauvegarde.Ce type d'approche permet d'écrire des fonctions avec des responsabilités uniques:
doonall
la responsabilité de est d'exécuter quelque chose sur tous ses arguments, un à la fois;backup
La responsabilité de est de faire une copie de son (unique) argument dans un répertoire de sauvegarde. Les deuxdoonall
etbackup
peuvent être utilisés dans d'autres contextes, ce qui permet une plus grande réutilisation du code, de meilleurs tests, etc.Dans ce cas, le rappel est la
backup
fonction, que nous disonsdoonall
de «rappeler» sur chacun de ses autres arguments - nous fournissons ledoonall
comportement (son premier argument) ainsi que les données (les arguments restants).(Notez que dans le type de cas d'utilisation démontré dans le deuxième exemple, je n'utiliserais pas le terme «rappel» moi-même, mais c'est peut-être une habitude résultant des langages que j'utilise. Je pense que cela passe des fonctions ou des lambdas autour , plutôt que d'enregistrer des rappels dans un système orienté événement.)
la source
Tout d'abord, il est important de noter que ce qui fait d'une fonction une fonction de rappel est la façon dont elle est utilisée, pas ce qu'elle fait. Un rappel se produit lorsque le code que vous écrivez est appelé à partir de code que vous n'avez pas écrit. Vous demandez au système de vous rappeler lorsqu'un événement particulier se produit.
Les pièges sont un exemple de rappel dans la programmation shell. Un piège est un rappel qui n'est pas exprimé comme une fonction, mais comme un morceau de code à évaluer. Vous demandez au shell d'appeler votre code lorsque le shell reçoit un signal particulier.
Un autre exemple de rappel est l'
-exec
action de lafind
commande. Le travail de lafind
commande est de parcourir les répertoires de manière récursive et de traiter chaque fichier tour à tour. Par défaut, le traitement consiste à imprimer le nom du fichier (implicite-print
), mais avec-exec
le traitement consiste à exécuter une commande que vous spécifiez. Cela correspond à la définition d'un rappel, bien que lors des rappels, ce n'est pas très flexible car le rappel s'exécute dans un processus distinct.Si vous avez implémenté une fonction de recherche, vous pouvez lui faire utiliser une fonction de rappel pour appeler chaque fichier. Voici une fonction de recherche ultra simplifiée qui prend un nom de fonction (ou un nom de commande externe) comme argument et l'appelle sur tous les fichiers normaux du répertoire en cours et de ses sous-répertoires. La fonction est utilisée comme un rappel qui est appelé chaque fois
call_on_regular_files
qu'un fichier normal est trouvé.Les rappels ne sont pas aussi courants dans la programmation shell que dans certains autres environnements car les shells sont principalement conçus pour des programmes simples. Les rappels sont plus courants dans les environnements où les données et le flux de contrôle sont plus susceptibles de se déplacer d'avant en arrière entre les parties du code qui sont écrites et distribuées indépendamment: le système de base, diverses bibliothèques, le code d'application.
la source
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; }
que vous pouvez exécuter commeforeach_server echo
,foreach_server nslookup
, etc.declare callback="$1"
est à peu près aussi simple que cela peut arriver cependant: le rappel doit être passé quelque part, ou ce n'est pas un rappel.Les "rappels" ne sont que des fonctions passées en arguments à d'autres fonctions.
Au niveau du shell, cela signifie simplement des scripts / fonctions / commandes passés en arguments à d'autres scripts / fonctions / commandes.
Maintenant, pour un exemple simple, considérons le script suivant:
avoir le synopsis
s'appliquera
filter
à chaquefile
argument, puis appelleracommand
avec les sorties des filtres comme arguments.Par exemple:
C'est très proche de ce que vous pouvez faire en lisp (je plaisante ;-))
Certaines personnes insistent pour limiter le terme "rappel" au "gestionnaire d'événements" et / ou à la "fermeture" (fonction + données / environnement tuple); ce n'est en aucun cas le sens généralement accepté . Et l'une des raisons pour lesquelles les «rappels» dans ces sens étroits ne sont pas très utiles dans le shell est que les canaux + parallélisme + capacités de programmation dynamique sont tellement plus puissants, et vous les payez déjà en termes de performances, même si vous essayez d'utiliser le shell comme une version maladroite de
perl
oupython
.la source
%
interpolation dans les filtres, la chose pourrait être réduite à:cmd=$1; shift; flt=$1; shift; $cmd <($flt "$1") <($flt "$2")
. Mais c'est beaucoup moins utile et illustratif à mon humble avis.$1 <($2 "$3") <($2 "$4")
En quelque sorte.
Une façon simple d'implémenter un rappel dans bash, est d'accepter le nom d'un programme comme paramètre, qui agit comme une "fonction de rappel".
Cela serait utilisé comme ceci:
Bien sûr, vous n'avez pas de fermetures en bash. Par conséquent, la fonction de rappel n'a pas accès aux variables côté appelant. Vous pouvez toutefois stocker les données dont le rappel a besoin dans des variables d'environnement. Il est plus difficile de renvoyer des informations du rappel au script d'invocateur. Les données pourraient être placées dans un fichier.
Si votre conception permet que tout soit géré dans un seul processus, vous pouvez utiliser une fonction shell pour le rappel, et dans ce cas, la fonction de rappel a bien sûr accès aux variables du côté invocateur.
la source
Juste pour ajouter quelques mots aux autres réponses. La fonction de rappel fonctionne sur des fonctions externes à la fonction qui rappelle. Pour que cela soit possible, soit une définition complète de la fonction à rappeler doit être transmise à la fonction rappelant, soit son code doit être disponible pour la fonction rappelant.
Le premier (passer du code à une autre fonction) est possible, mais je vais sauter un exemple car cela impliquerait de la complexité. Cette dernière (en passant le nom de la fonction) est une pratique courante, car les variables et fonctions déclarées en dehors de la portée d'une fonction sont disponibles dans cette fonction tant que leur définition précède l'appel à la fonction qui les opère (qui, à son tour , à déclarer avant son appel).
Notez également qu'une chose similaire se produit lorsque les fonctions sont exportées. Un shell qui importe une fonction peut avoir un framework prêt et n'attendre que les définitions de fonction pour les mettre en action. L'exportation de fonction est présente dans Bash et a causé des problèmes auparavant graves, btw (qui s'appelait Shellshock):
Je vais compléter cette réponse avec une autre méthode pour passer une fonction à une autre fonction, qui n'est pas explicitement présente dans Bash. Celui-ci le transmet par adresse, pas par nom. Cela peut être trouvé en Perl, par exemple. Bash n'offre cette méthode ni pour les fonctions, ni pour les variables. Mais si, comme vous le dites, vous voulez avoir une image plus large avec Bash comme exemple, alors vous devez savoir que le code de fonction peut résider quelque part dans la mémoire et que ce code est accessible par cet emplacement de mémoire, qui est appelé son adresse.
la source
L'un des exemples les plus simples de rappel en bash est celui que beaucoup de gens connaissent mais ne réalisent pas quel modèle de conception ils utilisent réellement:
cron
Cron vous permet de spécifier un exécutable (un binaire ou un script) que le programme cron rappellera lorsque certaines conditions seront remplies (la spécification d'heure)
Disons que vous avez un script appelé
doEveryDay.sh
. La méthode sans callback pour écrire le script est:La méthode de rappel pour l'écrire est simplement:
Ensuite, dans crontab, vous définissez quelque chose comme
Vous n'auriez alors pas besoin d'écrire le code pour attendre que l'événement se déclenche, mais vous devriez plutôt compter
cron
pour rappeler votre code.Maintenant, considérez COMMENT vous écririez ce code en bash.
Comment exécuteriez-vous un autre script / fonction dans bash?
Écrivons une fonction:
Vous venez de créer une fonction qui accepte un rappel. Vous pouvez simplement l'appeler comme ceci:
Bien sûr, la fonction toutes les 24 heures ne revient jamais. Bash est un peu unique en ce sens que nous pouvons très facilement le rendre asynchrone et générer un processus en ajoutant
&
:Si vous ne voulez pas cela en tant que fonction, vous pouvez le faire comme un script à la place:
Comme vous pouvez le voir, les rappels en bash sont triviaux. C'est simplement:
Et appeler le rappel est simplement:
Comme vous pouvez le voir ci-dessus, les rappels sont rarement directement des fonctionnalités des langues. Ils programment généralement de manière créative en utilisant les fonctionnalités linguistiques existantes. Tout langage capable de stocker un pointeur / une référence / une copie d'un bloc de code / d'une fonction / d'un script peut implémenter des rappels.
la source
watch
etfind
(lorsqu'ils sont utilisés avec le-exec
paramètre)Un rappel est une fonction appelée lorsqu'un événement se produit. Avec
bash
, le seul mécanisme de gestion des événements en place est lié aux signaux, à la sortie du shell et étendu aux événements d'erreurs du shell, aux événements de débogage et aux événements de fonction / scripts renvoyés.Voici un exemple d'un rappel inutile mais simple exploitant des pièges à signaux.
Créez d'abord le script implémentant le rappel:
Exécutez ensuite le script dans un terminal:
$ ./callback-example
et sur un autre, envoyer le
USR1
signal au processus shell.Chaque signal envoyé devrait déclencher l'affichage de lignes comme celles-ci dans le premier terminal:
ksh93
, en tant que shell implémentant de nombreuses fonctionnalités qui ontbash
été adoptées par la suite, fournit ce qu'il appelle des «fonctions de discipline». Ces fonctions, non disponibles avecbash
, sont appelées lorsqu'une variable shell est modifiée ou référencée (c'est-à-dire lue). Cela ouvre la voie à des applications événementielles plus intéressantes.Par exemple, cette fonctionnalité a permis aux rappels de style X11 / Xt / Motif sur les widgets graphiques d'être implémentés dans une ancienne version des
ksh
extensions graphiques appeléesdtksh
. Voir le manuel dksh .la source