Les Secrets sombres et cachés de Bash <author>Ben Okopnik <!-- Compléter: Linux Gazette n°55 - Traducteur:Fabien Ninoles <fabien@tzone.org> --> <!-- L'article sera coupé ici... --> <sect>Les Secrets sombres et cachés de Bash <p>Par Ben Okopnik <tt>ben-fuzzybear@yahoo.com</tt></p> <!-- La traduction --> <p><quote> <p>"Il y a deux produits majeurs qui sont sorties de Berkeley: le LSD et UNIX. Nous ne pensons pas que ce soit une coïncidence."</p> -- Jeremy Anderson </quote> </p> <p>Dans les profondeurs de la page de manuel de <code>bash</code> se terrent des choses terribles qui ne doivent être approchées ni par le timide ou l'inexpérimenté... Attention, Pèlerin: le dernier explorateur imprudent a avoir plongé dans ces régions mystérieuses a été retrouvés, des semaines plus tard, en train de spalmodier d'étranges incantations qui sonnaient comme &lquo;nullglob&rquo;, &lquo;dotglob&rquo;, et &lquo;MAILPATH='/usr/spool/mail/bfox?"You have mail":~/shell-mail?"$_ has mail!"'&rquo; (Il a par la suite été immédiatement engagé par une Compagnie Sans Nom dans la Silicon Valley pour un salaire non divulgué (mais énorme)... mais c'est hors du sujet) <p>Et alors, quel intérêt? Je suis déjà allé faire du paravoile et de la plongée sous-marine ce mois-ci (et je partirai bientôt pour une traversée de 500 milles sur le Gulf Stream); Vivons La Vida Loca! <url url="../gx/dennis/smily.gif" name="<SOURIEZ>"></p> <sect1> <heading>Expensions des paramètres</heading> <p>Les capacités internes de l'analyseur syntaxique de <code>bash</code> sont plutôt minimale comparées à, par exemple, <code>perl</code> ou <code>awk</code>: même dans ma meilleure estimation, elles ne sont pas fait pour des tâches sérieuses, juste assez pour s'occuper des tâches mineures. Quoiqu'il en soit, elles sont tout de même très utiles à cette tâche.</p> <p>Disons, comme example, que nous avons besoin de différencier les noms de fichiers en masjuscules de ceux en minuscules en analysant un répertoire - il m'est arrivé de faire cela pour mes fonds d'écran sous X, puisque certains ont l'air mieux en mosaïque et d'autres étirés en plein écran (la taille du fichier n'étant pas un bon guide à cet effet). J'ai mis en majuscule tous les noms des images devant être mis en plein-écran, et j'ai mis en minuscules toutes les mosaïques. Puis, au sein de mon sélectionneur de fonds d'écran aléatoires, <code>bkgr</code>, j'ai mis le code suivant:</p> <p><tscreen><verb> fn=$(basename $fnm) # Nous n'avons besoin _que_ du # nom du fichier. [ -z ${fn##[A-Z]*} ] && MAX="-max" # Ajoute l'option "-max" # si c'est vrai. xv -root -quit $MAX $fnm & # Éxécuter "xv" avec|sans "-max" # selon le résultat du test. </verb></tscreen></p> <p>Plutôt confus, n'est-ce pas? Bien, il y a déjà une partie que nous connaissons: le [ -z ... ] est un test pour une chaîne de caractères vide. Alors, que signifie l'autre partie?</p> <p>Afin de 'protéger' le résultat d'expansion de paramètres du monde froid et cruel (e.g., si vous voulez utiliser le résultat dans un nom de fichier, vous avez besoin de cette protection pour garder les caractères séparés des autres caractères), on utilise les accolades pour entourer tout l'enchilada. <bf>$d</bf> est équivalent à <bf>${d}</bf> excepté que la deuxième espèce peut être combinée à d'autres choses sans perdre son identité - comme dans:</p> <p><tscreen><verb> d=Ros echo ${d}i # "Rosi" echo ${d}e # "Rose" echo ${d}a # "Rosa" echo ${d}alis # "Rosalis" </verb></tscreen></p> <p>Maintenant que nous l'avons isolé du reste du monde, sans ami et seul... oups, désolé - c'est un "script de _shell_", pas un "script de film d'horreur" - je perds le fil une fois de temps en temps... En tout cas, maintenant que nous avons séparé la variable à l'aide des accolades, nous pouvons appliquer quelques outils intégrés à <code>bash</code> (un petit malin, n'est-ce pas?) pour faire quelques manipulation sur sa valeur. En voici la liste (pour cet exercice, nous assumons que $parametre="amanuensis") :</p> <p>${#parametre} - retourne la longueur de la valeur du paramètre.</p> <p>EXAMPLE: <tt>${#parametre} = 10</tt></p> <p>${parametre#motif} - retire la plus petite correspondance à partir du début du paramètre.</p> <p>EXAMPLE: <tt>${parametre#*n} = uensis</tt></p> <p>${parametre##motif} - retire la plus grande correspondance à partir du début du paramètre.</p> <p>EXAMPLE:<tt> ${parametre#*n} = sis</tt></p> <p>${parametre%motif} - retire la plus petite correspondance à partir de la fin du paramètre</p> <p>EXAMPLE:<tt> ${parametre%n*} = amanue</tt></p> <p>${parametre%%motif} - retire la plus grande correspondance à partir de la fin du paramètre</p> <p>EXAMPLE:<tt> ${parametre%%n*} = ama</tt></p> <p>${parametre:decalage} - retourne la valeur du paramètre débutant à `decalage'.</p> <p>EXAMPLE: <tt>${parametre:7} = sis</tt></p> <p>${parametre:decalage:longueur} - retourne 'lougueur' caractères commençant à `decalage'.</p> <p>EXAMPLE: <tt>${parametre:1:3} = man</tt></p> <p>${parametre/motif/substitut} - remplace la première correspondance.</p> <p>EXAMPLE: <tt>${parametre/amanuen/paralip} = paralipsis</tt></p> <p>${parametre//motif/substitut} - remplace toutes les correspondances.</p> <p>EXAMPLE:<tt> ${parametre//a/A} = AmAnuensis</tt></p> <p>(Pour les deux dernières opérations, si le motif commence avec #, il correspondra au début de la chaîne; si il commence avec %, il correspondra à la fin de la chaîne. Si substitut est vide, les correspondances seront effacées.)</p> <p>Il y a actuellement un peu plus que ça - des trucs comme des variables d'indirection et des vecteurs - mais alors, j'imagine que vous n'avez qu'à étudiez le manuel vous-même. <url url="../gx/dennis/smily.gif" name="<SOURIEZ>"> considérez simplement ceci comme du matériel de motivation.</p> <p>Bon, maintenant que nous avons découvert les outils, examinons à nouveau le code -</p> <p><tscreen><verb> [ -z ${fn##[A-Z]*} ] </verb></tscreen></p> <p>Pas <em>si</em> difficile que ça maintenant, n'est-ce pas? Ou peut-être que si; mon processus de pensée, lorsque j'ai affaire à des recherche et correspondance de motifs, tend à ressembler à un pretzel. Ce que j'ai fait ici - et on pourrait trouver de nombreuses autres façons en utilisant les outils ci-dessus - est de trouver la plus grande correspondance de la chaîne (i.e. le nom entier du fichier) qui commencent avec une majuscule. Le <code>[ -z ... ]</code> retourne vrai si le résultat est nul (i.e. correspond au motif <code>[A-Z]*</code>), et $MAX est alors mis à "-max".</p> <p>Il est à noter que comme nous faisons correspondre la chaîne en entier, <code>${fn%%[A-Z]*}</code> marcherait tout autant. Si cela vous semble confus - si <em>tout</em> ce qu'il y a ci-dessus vous semble confus - je vous suggère d'expérimenter énormément afin de vous familiariser vous-même avec. C'est facile: mettez une valeur à un paramètre et expérimentez, tels que -</p> <p><tscreen><verb> Odin:~$ experiment=supercallifragilisticexpialadocious Odin:~$ echo ${experiment%l*} supercallifragilisticexpia Odin:~$ echo ${experiment%%l*} superca Odin:~$ echo ${experiment#*l} lifragilisticexpialadocious Odin:~$ echo ${experiment##*l} adocious </verb></tscreen></p> <p>... etc. C'est la meilleur façon de bien sentir ce que fait un outil; prenez-en un, insérez-le, mettez vos verres de sûreté et appuyez doucement sur la gâchette. Observez toutes les consignes de sécurité puisqu'un effacement aléatoire de données de valeur peut toujours arriver. Les résultats actuels peuvent varier et vous surprendront souvent.</p> </sect1> <sect1> <heading>États du paramètre</heading> <p>Parfois - par exemple en voulant vérifier certaines conditions d'erreurs sur les valeurs assignées à des variables - nous avons besoin de savoir si une variable spécifique a été assigné à une valeur ou non. On pourrait toujours vérifier la longueur de la valeur, comme j'ai fait précédemment, mais les utilitaires offerts par <code>bash</code> à cet effet nous fournissent des raccourcis utiles pour de telles occasions:</p> <p>(Ici, nous assumerons que notre variable - $joe - n'a pas été assignée ou a une valeur nulle.)</p> <p>${parametre:-autre} - Si parametre n'est pas assignée, "autre" est substitué.</p> <p>EXAMPLE: <tt>${joe:-mary} = mary </tt>($joe reste non-assignée.)</p> <p>${parametre:=autre} - Si parametre n'est pas assignée, assigne-lui "autre" et retourne la valeur de paramètre.</p> <p>EXAMPLE: <tt>${joe:=mary} = mary </tt>($joe="mary".) <p>${parametre:?autre} - Affiche "autre" ou provoque une erreur si parametre n'est pas assignée.</p> <p>EXAMPLE:</p> <p><tscreen><verb> Odin:~$ echo ${joe:?"Not set"} bash: joe: Not set Odin:~$ echo ${joe:?} bash: joe: parameter null or not set </verb></tscreen></p> <p>${parametre:+autre} - "autre" remplace parametre si parametre est assignée.</p> <p>EXAMPLE:</p> <p><tscreen><verb> Odin:~$ joe=blahblah Odin:~$ echo ${joe:+mary} mary Odin:~$ echo $joe blahblah </verb></tscreen></p> </sect1> <sect1> <heading>Manipulation de vecteurs</heading> <p>Une autre capacité interne de <code>bash</code>, un mécanisme de base pour manipuler des vecteurs ou tableaux de données, nous permet d'analyser des données qui doivent être indexées, ou au moins gardées dans une structure qui permet un adressage individuel de chacun de ses membres. Considérez le scénario suivant: si j'ai un bottin d'adresse et que je veux envoyer ma dernière "Rubrique du Marin" à tous ceux dans la catégorie "Amis", comment je fais? De plus, disons que je veux créer une liste de noms des personnes à qui je l'ai envoyée... ou d'autres formes d'analyse... i.e. qu'il devienne nécessaire de la séparé en champs selon la longueur, et les vecteurs deviennent rapidement une des seules options viables.</p> <p>Regardons ce que ça peut impliquer. Voici un extrait du bottin utilisé pour ce travail:</p> <p><tscreen> <bf>Nom Catégorie Adresse e-mail</bf><verb> Jim &ero; Fanny Friends Affaires 101101 Digital Dr. LA CA fr@gnarly.com Fred &ero; Wilma Rocks amis 12 Cave St. Granite, CT shale@hill.com Joe 'Da Fingers' Lucci Affaires 45 Caliber Av. B-klyn NY tuff@ny.org Yoda Leahy-Hu Amis 1 Peak Fribourg Switz. warble@sing.ch Cyndi, Wendi, &ero; Myndi Affaires 5-X Rated St. Holiday FL 3cuties@fl.net </verb></tscreen></p> <p>Ouf. oN voit bien que l'on doit lire ça par champs, compter les mots ne marcheraient pas; pas même une recherche de texte. Les vecteurs viennent ici nous sauver!</p> <p><tscreen><verb> #!/bin/bash # 'nlmail' envoie les nouvelles mensuelles à une liste d'amis inclus # dans le bottin. # bash créerait les vecteurs automatiquement, puisque nous utilisons # la syntaxe 'nom[index]' pour charger les variables. Il se trouve # toutefois que j'aime les déclarations explicites. declare -a name category address email # Compte le nombre de lignes dans le bottin et boucle ce nombre de # fois. for x in $(seq $(grep -c $ bottin)) do x=$(($x)) # Change x en nombre line="$(sed -n ${x}p bottin)" # Imprime la ligne x name[$x]="${line:0:25}" # Charge la variable nom category[$x]="${line:25:10}" # Etc., address[$x]="${line:35:25}" # etc., email[$x]="${line:60:20}" # etc. done # Continuer plus loin.... </verb></tscreen></p> <p>À ce point, nous avons le fichier "bottin" chargé dans quatre vecteurs que nous avons créés, prêt à être analysés. Chacun des champs est facilement adressable, permettant une solution triviale au problème d'envoyer le fichier à tous mes amis (cet extrait est la suite du script précédent):</p> <p><tscreen><verb> # Suite de ci-dessus ... # Continued from above ... for y in $(seq $x) do # Nous ferons correspondre le mot "amis" dans le champ # 'catégorie', le rendant insensible à la casse et en retirant les # caractères qui resterait. if [ -z $(echo ${category[$y]##[Ff]riend*}) ] then mutt -a Newsletter.pdf -s 'S/V Ulysses News, 6/2000' ${email[$y]} echo "Mail envoyé à ${name[$y]}" >> liste_envoie.txt fi done </verb></tscreen></p> <p>Ça devrait faire l'affaire, en plus de copier le nom des récipients dans un fichier appelé "liste_envoie.txt" - une chouette caractéristique pour vérifier si on a oublié personne.</p> <p>Les capacités de manipulation de vecteurs de <code>bash</code> s'étend un peu au-delà de cet exemple simple. Suffit-il de dire que pour des cas simples de cette sorte, avec des fichiers en-dessous de disons une centaine de kB, les vecteurs <code>bash</code> sont à recommander. Pour satisfaire ma curiosité, j'ai créé une liste de noms qui est juste un peu plus grande que 100kBm en utilisant le bottin de l'example précédent:</p> <p><tscreen><verb> for n in $(seq 300); do cat bottin >> liste_nom; done </verb></tscreen></p> <p>que j'aie ensuite roulé sur mon vieux Pentium 233/64MB. 24 secondes; pas si pire pour 1500 enregistrements et un outil "bricolé".</p> <p>Noter que le script ci-dessous peut facilement être généralisé, par example, en ajoutant l'option de spécifier différents bottins, critères ou actions, directement de la ligne de commande. Une fois les données divisées dans un format plus facilement adressable, les possibilités sont infinies.</p> </sect1> <sect1> <heading>Pour l'emballage...</heading> <p><code>bash</code>, en plus d'être très puissant dans son rôle d'interpréteur de ligne de commande et de shell, possède aussi un grand nombre d'outils sophistiqués pour tous ceux qui ont besoin de créer leurs propres programmes. À mon avis, les scripts shell remplissent leur niche - celle d'un langage de programmation simple mais puissant - parfaitement, juste entre la ligne de commande et la programmation complète (C, Tcl/Tk, Python), et devrait donc faire partie de l'arsenal de tous usagers de *nix. Linux, spécifiquement, semble encourager l'attitude "fais le toi-même" parmi ses usagers, en leur donnant accès à des outils puissants et les moyens d'automatiser leur usage: quelque chose que je considère une plus grande intégration (et un plus grand "quotient d'utilité") entre la puissance du Système d'Exploitation et l'environnement de l'usager. "La Puissance au Peuple!" <url url="../gx/dennis/smily.gif" name="<SOURIEZ>"></p> <p><quote> Until next month - Happy Linuxing! </quote></p> </sect1> <sect1> <heading>Citation du mois</heading> <p>"...Aussi terrible qu'une dépendance à UNIX peut être, il y a des destins pire. Si UNIX est l'héroïne des systèmes d'exploitation, alors VMS est une dépendance aux barbituriques, le Mac est du MDMA, et MS-DOS est comme respirer de la colle. (Windows est de remplir vos sinus de lucite et de la laisser sécher.)"</p> <p>You owe the Oracle a twelve-step program."</p> <p><cite>--The Usenet Oracle</cite></p> </sect1> <sect1> <heading>Références</heading> <p>Les pages de manuel ("man") de <code>bash</code>, l'aide en ligne.</p> <p><A HREF="../issue52/okopnik.html">"Introduction aux scripts shell - La base" by Ben Okopnik, LG #52</A></p> <p><A HREF="../issue53/okopnik.html">"Introduction aux scripts shell" by Ben Okopnik, LG #53</A></p> <p><A HREF="../issue54/okopnik.html">"Introduction aux scripts shell" by Ben Okopnik, LG #54</A></p> <p>Copyright 2000, Ben Okopnik. Paru dans le numéro 55 de la Linux Gazette de Juillet 2000. <p>Traduction française de Fabien Niñoles <tt>fabien@tzone.org</tt> </sect> <!-- ...et ici --> </article> <!-- Keep this comment at the end of the file Local variables: mode: sgml sgml-omittag:t sgml-shorttag:t sgml-namecase-general:t sgml-general-insert-case:lower sgml-minimize-attributes:nil sgml-always-quote-attributes:t sgml-indent-step:0 sgml-indent-data:t sgml-parent-document:nil sgml-exposed-tags:nil sgml-local-catalogs:nil sgml-local-ecat-files:nil sgml-default-dtd-file:"~/.sgml/linuxdoc.ced" End: -->