Au travail, j'écris fréquemment des scripts bash. Mon superviseur a suggéré que le script entier soit divisé en fonctions, comme dans l'exemple suivant:
#!/bin/bash
# Configure variables
declare_variables() {
noun=geese
count=three
}
# Announce something
i_am_foo() {
echo "I am foo"
sleep 0.5
echo "hear me roar!"
}
# Tell a joke
walk_into_bar() {
echo "So these ${count} ${noun} walk into a bar..."
}
# Emulate a pendulum clock for a bit
do_baz() {
for i in {1..6}; do
expr $i % 2 >/dev/null && echo "tick" || echo "tock"
sleep 1
done
}
# Establish run order
main() {
declare_variables
i_am_foo
walk_into_bar
do_baz
}
main
Y a-t-il une raison de faire cela autre que la "lisibilité", qui, à mon avis, pourrait être tout aussi bien établie avec quelques commentaires supplémentaires et un espacement des lignes?
Cela rend-il le script plus efficace (je pense en réalité le contraire), ou facilite-t-il la modification du code au-delà du potentiel de lisibilité susmentionné? Ou est-ce vraiment juste une préférence stylistique?
Veuillez noter que, bien que le script ne le montre pas bien, "l'ordre d'exécution" des fonctions de nos scripts actuels tend à être très linéaire: cela walk_into_bar
dépend des choses que vous avez i_am_foo
faites et do_baz
agit walk_into_bar
comme pouvoir échanger arbitrairement l'ordre d'exécution n'est généralement pas ce que nous ferions. Par exemple, vous ne voudriez pas soudainement mettre declare_variables
après walk_into_bar
, cela casserait les choses.
Voici un exemple d'écriture du script ci-dessus:
#!/bin/bash
# Configure variables
noun=geese
count=three
# Announce something
echo "I am foo"
sleep 0.5
echo "hear me roar!"
# Tell a joke
echo "So these ${count} ${noun} walk into a bar..."
# Emulate a pendulum clock for a bit
for i in {1..6}; do
expr $i % 2 >/dev/null && echo "tick" || echo "tock"
sleep 1
done
la source
main()
en haut et ajoutermain "$@"
en bas pour l'appeler. Cela vous permet de voir la logique de script de haut niveau en tout temps lorsque vous l'ouvrez.local
- ceci fournit une étendue de variable extrêmement importante pour tout script non trivial.Réponses:
J'ai commencé à utiliser ce même style de programmation bash après avoir lu le billet de blog "Defensive Bash Programming" de Kfir Lavi . Il donne quelques bonnes raisons, mais personnellement, je trouve celles-ci les plus importantes:
les procédures deviennent descriptives: il est beaucoup plus facile de déterminer ce qu’une partie du code est censée faire. Au lieu d'un mur de code, vous voyez "Oh, la
find_log_errors
fonction lit ce fichier journal pour rechercher les erreurs". Comparez cela avec la recherche d'un grand nombre de lignes awk / grep / sed qui utilisent dieu sait quel type de regex au milieu d'un long script - vous n'avez aucune idée de ce que ça fait là-bas sauf s'il y a des commentaires.vous pouvez déboguer des fonctions en incluant dans
set -x
etset +x
. Une fois que vous savez que le reste du code fonctionne correctement, vous pouvez utiliser cette astuce pour vous concentrer sur le débogage de cette fonction spécifique uniquement. Bien sûr, vous pouvez inclure des parties de script, mais que faire si c'est une longue partie? C'est plus facile de faire quelque chose comme ça:utilisation de l'impression avec
cat <<- EOF . . . EOF
. Je l'ai utilisé plusieurs fois pour rendre mon code beaucoup plus professionnel. En outre,parse_args()
avec lagetopts
fonction est très pratique. Encore une fois, cela aide à la lisibilité, au lieu de tout placer dans le script en tant que mur de texte géant. Il est également pratique de les réutiliser.Et bien évidemment, ceci est beaucoup plus lisible pour quelqu'un qui connaît C ou Java, ou Vala, mais qui a une expérience limitée en matière de jeu. En ce qui concerne l'efficacité, il n'y a pas beaucoup de choses que vous puissiez faire - bash n'est pas le langage le plus efficace et les gens préfèrent perl et python en termes de rapidité et d'efficacité. Cependant, vous pouvez avoir
nice
une fonction:Comparé à un appel gentil sur chaque ligne de code, cela réduit considérablement le typage ET peut être utilisé de manière pratique lorsque vous souhaitez qu'une partie seulement de votre script s'exécute avec une priorité inférieure.
L'exécution de fonctions en arrière-plan, à mon avis, est également utile lorsque vous souhaitez que de nombreuses instructions soient exécutées en arrière-plan.
Certains des exemples où j'ai utilisé ce style:
la source
local
et en appelant tout via lamain()
fonction. Cela rend les choses beaucoup plus faciles à gérer et vous pouvez éviter une situation potentiellement compliquée.La lisibilité est une chose. Mais la modularisation ne se limite pas à cela. (La semi-modularisation est peut-être plus correcte pour les fonctions.)
Dans les fonctions, vous pouvez garder certaines variables locales, ce qui augmente la fiabilité et réduit les risques d’embrouillage.
Un autre avantage des fonctions est la réutilisation . Une fois qu'une fonction est codée, elle peut être appliquée plusieurs fois dans le script. Vous pouvez également le porter sur un autre script.
Votre code peut maintenant être linéaire, mais à l'avenir, vous pourrez entrer dans le domaine du multi-threading ou du multi-traitement dans le monde Bash. Une fois que vous apprenez à faire des choses dans les fonctions, vous serez bien équipé pour entrer dans le parallèle.
Un dernier point à ajouter. Comme le remarque Etsitpab Nioliv dans le commentaire ci-dessous, il est facile de rediriger les fonctions en tant qu’entité cohérente. Mais il y a un autre aspect des redirections avec des fonctions. À savoir, les redirections peuvent être définies dans la définition de la fonction. Par exemple.:
Maintenant, aucune redirection explicite n'est nécessaire pour les appels de fonction.
Cela peut épargner de nombreuses répétitions, ce qui augmente encore la fiabilité et aide à garder les choses en ordre.
Voir également
la source
source
ou. scriptname.sh
, et d'utiliser ces fonctions telles quelles, si elles étaient dans votre nouveau script.Dans mon commentaire, j'ai mentionné trois avantages des fonctions:
Ils sont plus faciles à tester et à vérifier leur exactitude.
Les fonctions peuvent être facilement réutilisées (source) dans les futurs scripts
Votre patron les aime.
Et ne sous-estimez jamais l’importance du chiffre 3.
J'aimerais aborder un autre problème:
Pour tirer le meilleur parti du code, c’est-à-dire rendre les fonctions aussi indépendantes que possible. Si
walk_into_bar
nécessite une variable qui n’est pas utilisée ailleurs, cette variable doit alors être définie et localisée danswalk_into_bar
. Le processus de séparation du code en fonctions et de réduction de leurs interdépendances devrait rendre le code plus clair et plus simple.Idéalement, les fonctions devraient être faciles à tester individuellement. Si, en raison des interactions, ils ne sont pas faciles à tester, c'est un signe qu'ils pourraient bénéficier de la refactorisation.
la source
;-)
Vous divisez le code en fonctions pour la même raison que vous le feriez pour C / C ++, Python, Perl, Ruby ou tout autre code de langage de programmation. La raison la plus profonde est l'abstraction - vous encapsulez des tâches de niveau inférieur dans des primitives de niveau supérieur (fonctions), de sorte que vous n'ayez pas à vous soucier de la façon dont les choses sont accomplies. En même temps, le code devient plus lisible (et maintenable), et la logique du programme devient plus claire.
Cependant, en regardant votre code, je trouve assez étrange d’avoir une fonction pour déclarer des variables; cela me fait vraiment lever un sourcil.
la source
main
fonction / méthode, alors?Bien que je sois totalement d’accord avec la réutilisabilité , la lisibilité et le fait d’embrasser délicatement les patrons, il existe un autre avantage des fonctions de bash : la portée variable . Comme le montre le LDP :
Je ne vois pas cela très souvent dans les scripts shell du monde réel, mais cela semble être une bonne idée pour des scripts plus complexes. La réduction de la cohésion aide à éviter les bugs où vous écrasez une variable attendue dans une autre partie du code.
La réutilisation signifie souvent créer une bibliothèque commune de fonctions et l'intégrer
source
dans tous vos scripts. Cela ne les aidera pas à courir plus vite, mais cela vous aidera à les écrire plus rapidement.la source
local
, mais je pense que la plupart des gens qui écrivent des scripts divisés en fonctions suivent toujours le principe de conception. Usignlocal
rend simplement plus difficile l'introduction de bugs.local
rend les variables disponibles pour la fonction et ses enfants, il est donc très agréable d'avoir une variable qui peut être transmise de la fonction A, mais non disponible pour la fonction B, qui peut vouloir avoir une variable du même nom mais avec un but différent. Donc c'est bon pour définir la portée, et comme l'a dit Voo - moins de bugsUne raison complètement différente de celles déjà mentionnées dans d’autres réponses: une des raisons pour laquelle cette technique est parfois utilisée, où la seule instruction de définition non fonctionnelle au niveau supérieur est un appel à
main
, est de s’assurer que le script ne fait rien par inadvertance. si le script est tronqué. Le script peut être tronqué s'il passe du processus A au processus B (le shell) et que le processus A se termine pour une raison quelconque avant la fin de l'écriture de l'intégralité du script. Cela est particulièrement susceptible de se produire si le processus A récupère le script à partir d'une ressource distante. Pour des raisons de sécurité, ce n’est pas une bonne idée, c’est quelque chose qui est fait et certains scripts ont été modifiés pour anticiper le problème.la source
main()
motif est habituel en Python où l’on utiliseif __name__ == '__main__': main()
à la fin du fichier.import
le script actuel sans s'exécutermain
. Je suppose qu'un garde similaire pourrait être mis dans un script bash.Un processus nécessite une séquence. La plupart des tâches sont séquentielles. Cela n'a aucun sens de jouer avec l'ordre.
Mais la grande chose à propos de la programmation - qui inclut les scripts - est le test. Essais, essais, essais. Quels scripts de test avez-vous actuellement pour valider l'exactitude de vos scripts?
Votre patron essaie de vous guider d’être un script kiddy à un programmeur. C’est une bonne direction à suivre. Les gens qui viendront après vous vous plairont.
MAIS. Rappelez-vous toujours vos racines orientées processus. S'il est judicieux de classer les fonctions dans l'ordre dans lequel elles sont généralement exécutées, faites-le au moins lors du premier passage.
Plus tard, vous verrez que certaines de vos fonctions traitent les entrées, les sorties, les traitements, les données de modélisation et les autres, il peut donc être judicieux de regrouper des méthodes similaires, voire de les déplacer dans des fichiers séparés. .
Plus tard, vous réaliserez peut-être que vous avez maintenant écrit des bibliothèques de petites fonctions d'assistance que vous utilisez dans beaucoup de vos scripts.
la source
Les commentaires et l’espacement ne peuvent s’approcher de la lisibilité des fonctions, comme je vais le montrer. Sans fonctions, vous ne pouvez pas voir la forêt pour les arbres - de gros problèmes se cachent parmi de nombreuses lignes de détail. En d'autres termes, les personnes ne peuvent pas se concentrer simultanément sur les détails les plus fins et sur la grande image. Cela peut ne pas être évident dans un court script; tant qu'il reste court, il peut être assez lisible. Les logiciels ne deviennent pas plus gros, mais font certainement partie de tout le système logiciel de votre entreprise, qui est sûrement beaucoup plus grand, probablement des millions de lignes.
Considérez si je vous ai donné des instructions telles que celle-ci:
Au moment où vous avez atteint la moitié, voire 5%, vous auriez oublié les premières étapes. Vous ne pouviez probablement pas identifier la plupart des problèmes, car vous ne pouviez pas voir la forêt pour les arbres. Comparer avec les fonctions:
C'est certainement beaucoup plus compréhensible, quel que soit le nombre de commentaires que vous pourriez insérer dans la version séquentielle ligne par ligne. Cela rend également beaucoup plus probable que vous remarquerez que vous avez oublié de préparer le café et probablement oublié sit_down () à la fin. Lorsque votre esprit pense aux détails des expressions rationnelles grep et awk, vous ne pouvez pas avoir une vue d'ensemble - "Et si on ne fait pas de café"?
Les fonctions vous permettent principalement de voir la situation dans son ensemble et de remarquer que vous avez oublié de préparer le café (ou que quelqu'un pourrait préférer le thé). À un autre moment, dans un autre état d'esprit, vous vous inquiétez de la mise en œuvre détaillée.
Il y a bien sûr d'autres avantages discutés dans d'autres réponses. Un autre avantage qui n’est pas clairement indiqué dans les autres réponses est que les fonctions fournissent une garantie importante pour la prévention et la correction des bogues. Si vous découvrez qu'une variable $ foo dans la fonction correcte walk_to () était fausse, vous savez qu'il suffit de regarder les 6 autres lignes de cette fonction pour rechercher tout ce qui aurait pu être affecté par ce problème et tout ce qui pourrait ont fait que ce soit faux. Sans fonctions (appropriées), tout et n'importe quoi dans tout le système pourrait être la cause de l'inexactitude de $ foo, et tout et n'importe quoi pourraient être affectés par $ foo. Par conséquent, vous ne pouvez pas réparer en toute sécurité $ foo sans réexaminer chaque ligne du programme. Si $ foo est local à une fonction,
la source
bash
syntaxe. C'est dommage cependant; Je ne pense pas qu'il y ait un moyen de passer des informations à des fonctions comme celle-là. (iepour();
<coffee
). Cela ressemble plus àc++
ouphp
(je pense).Quelques truismes pertinents sur la programmation:
Les commentaires commencent comme un moyen de ne pas être en mesure d'exprimer clairement vos idées dans le code * et empirent (ou sont tout simplement faux) avec le changement. Par conséquent, dans la mesure du possible, exprimez les concepts, les structures, le raisonnement, la sémantique, le flux, la gestion des erreurs et tout ce qui concerne la compréhension du code en tant que code.
Cela dit, certaines fonctions de Bash n’ont pas été trouvées dans la plupart des langues:
local
mot - clé, vous polluez l'espace de noms global.local foo="$(bar)"
entraîne la perte du code de sortie debar
."$@"
signifie dans différents contextes.* Je suis désolé si cela est offensant, mais après avoir utilisé des commentaires pendant quelques années et développé sans eux ** pendant plusieurs années, il est clair que ce qui est supérieur est.
** Il est toujours nécessaire d'utiliser des commentaires pour les licences, la documentation de l'API, etc.
la source
local foo=""
Puis les mettre en utilisant l' exécution de commande pour agir sur le résultat ...foo="$(bar)" || { echo "bar() failed"; return 1; }
. Cela nous permet de quitter rapidement la fonction lorsqu'une valeur requise ne peut pas être définie. Les accolades sont nécessaires pour s’assurer qu’ellesreturn 1
ne sont exécutées qu’en cas d’échec.Le temps, c'est de l'argent
Il existe d’ autres bonnes réponses qui permettent de mieux comprendre les raisons techniques pour écrire de manière modulaire un script potentiellement long, développé dans un environnement de travail, développé pour être utilisé par un groupe de personnes et pas uniquement pour votre propre usage.
Je veux me concentrer sur une attente: dans un environnement de travail "le temps, c'est de l'argent" . Ainsi, l’absence de bugs et les performances de votre code sont évaluées ainsi que la lisibilité , la testabilité, la maintenabilité, la refactorabilité, la réutilisabilité ...
Ecrire dans les "modules" un code réduira le temps de lecture nécessaire non seulement au codeur lui-même, mais même au temps utilisé par les testeurs ou par le boss. De plus, notez que le temps d'un patron est généralement mieux payé que le temps d'un codeur et que votre patron évaluera la qualité de votre travail.
De plus, écrire dans des "modules" indépendants un code (même un script bash) vous permettra de travailler "en parallèle" avec un autre composant de votre équipe, réduisant ainsi le temps de production global et utilisant au mieux les compétences du célibataire, pour réviser ou réécrire une pièce avec aucun effet secondaire sur les autres, pour recycler le code que vous venez d'écrire "tel quel"pour un autre programme / script, créer des bibliothèques (ou des bibliothèques d'extraits), réduire la taille globale et la probabilité d'erreurs associée, déboguer et tester minutieusement chaque partie ... et bien sûr, il organisera votre programme en section logique / script et améliorer sa lisibilité. Toutes les choses qui permettront d'économiser du temps et donc de l'argent. L'inconvénient est que vous devez vous en tenir aux normes et commenter vos fonctions (ce que vous devez néanmoins faire dans un environnement de travail).
Adhérer à une norme ralentira votre travail au début, mais accélérera ensuite le travail de tous les autres (et de votre vôtre). En effet, lorsque le nombre de personnes impliquées augmente, la collaboration devient un besoin inévitable. Ainsi, par exemple, même si je crois que les variables globales doivent être définies globalement et non dans une fonction, je peux comprendre un standard qui les inizialise dans une fonction
declare_variables()
appelée toujours dans la première ligne de celle-main()
ci ...Enfin et surtout, ne sous-estimez pas la possibilité, dans les éditeurs de code source modernes, d’afficher ou de masquer des routines séparées de manière sélective ( pliage du code ). Cela gardera le code compact et concentrera l’utilisateur sur le gain de temps.
Ci-dessus, vous pouvez voir comment se déroule uniquement la
walk_into_bar()
fonction. Même des autres lignes avaient une longueur de 1000 lignes chacune, vous pouviez toujours garder le contrôle de tout le code sur une seule page. Notez qu'il est plié même dans la section où vous allez déclarer / initialiser les variables.la source
Outre les raisons données dans d'autres réponses:
la source
Une autre raison souvent négligée est l'analyse syntaxique de bash:
Ce script contient évidemment une erreur de syntaxe et bash ne devrait pas l'exécuter du tout, non? Faux.
Si nous encapsulions le code dans une fonction, cela ne se produirait pas:
la source