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> <!-- La traduction --> <p> <quote> «~Il y a deux produits majeurs qui sont sortis de Berkeley~: le LSD et UNIX. Nous ne pensons pas que ce soit une coïncidence.~»<newline> --~Jeremy Anderson </quote> <p>Dans les profondeurs de la page de manuel de <tt/bash/ se terrent de terribles choses qui ne doivent être approchées ni par le timide, ni par 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 psalmodier d'étranges incantations qui sonnaient comme "nullglob", "dotglob", et "MAILPATH='/usr/spool/mail/bfox?"You have mail":~/shell-mail?"$_ has mail!"'" (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 en dehors 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~!~<tt/:-)/ <sect1>Expansions des paramètres <p>Les capacités de l'analyseur syntaxique de <tt/bash/ sont plutôt minimes comparées à, par exemple, celles de <tt/perl/ ou <tt/awk/. Pour ma part, et dans le meilleur des cas, cet analyseur syntaxique n'est pas fait pour des tâches sérieuses. Il sait en faire juste assez pour s'occuper des tâches mineures. Quoi qu'il en soit, il est tout de même très utile pour cela. <p>Disons, par exemple, que nous avons besoin de différencier les noms de fichiers en majuscules de ceux en minuscules en analysant un répertoire --~il m'est arrivé de faire cela pour mes fonds d'écran sous X~; certains rendent mieux sous forme de mosaïque et d'autres étirés sur tout l'écran (dans ce cas, la taille du fichier n'était pas un bon guide). 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, dans <tt/bkgr/, le programme qui change mes fonds d'écran aléatoirement, 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>Plutôt déconcertant, n'est-ce pas~? Bien, il y a déjà une partie que nous connaissons~: le <tt/[ -z ... ]/ est un test pour détecter une chaîne de caractères vide. Alors, que signifie l'autre partie~? <p>Afin de <em/protéger/ l'expansion du paramètre du monde froid et cruel (e.g., si vous vouliez utiliser le résultat dans un nom de fichier, vous auriez eu besoin de cette protection pour le conserver à l'écart des autres caractères), on utilise les accolades pour entourer tout ce morceau. <tt>$d</tt> est équivalent à <tt>${d}</tt> sauf que la deuxième forme peut être combinée à autre chose sans perdre son identité, comme dans~: <p><tscreen><verb> d=Ros echo ${d}i # "Rosi" echo ${d}e # "Rose" echo ${d}a # "Rosa" echo ${d}alis # "Rosalis" </verb></tscreen> <p>Maintenant que nous l'avons isolé du reste du monde, sans ami et seul... oups, désolé --~c'est un «~script de <em/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 à <tt/bash/ (un petit malin, n'est-ce pas~?) pour faire quelques manipulation sur sa valeur. En voici la liste (pour cet exercice, nous faisons l'hypothèse que <tt/$parametre="amanuensis"/)~: <tt/${#parametre}/~: retourne la longueur de la valeur du paramètre.<newline> Exemple~: <tt>${#parametre} = 10</tt> <tt/${parametre#motif}/~: supprime la plus petite occurence à partir du début du paramètre.<newline> Exemple~: <tt>${parametre#*n} = uensis</tt> <tt/${parametre##motif}/~: supprime la plus grande occurence à partir du début du paramètre.<newline> Exemple~:<tt> ${parametre#*n} = sis</tt> <tt/${parametre%motif}/~: supprime la plus petite occurence à partir de la fin du paramètre.<newline> Exemple~:<tt> ${parametre%n*} = amanue</tt> <tt/${parametre%%motif}/~: supprime la plus grande occurence à partir de la fin du paramètre.<newline> Exemple~:<tt> ${parametre%%n*} = ama</tt> <tt/${parametre:decalage}/~: retourne la valeur du paramètre débutant à <tt/decalage/.<newline> Exemple~: <tt>${parametre:7} = sis</tt> <tt/${parametre:decalage:longueur}/~: retourne <tt/lougueur/ caractères commençant à <tt/decalage/.<newline> Exemple~: <tt>${parametre:1:3} = man</tt> <tt>${parametre/motif/substitut}</tt>~: remplace la première occurence de <tt/motif/ par <tt/substitut/.<newline> Exemple~: <tt>${parametre/amanuen/paralip} = paralipsis</tt> <tt>${parametre//motif/substitut}</tt>~: remplace toutes les occurences de <tt/motif/ par <tt/substitut/.<newline> Exemple~:<tt> ${parametre//a/A} = AmAnuensis</tt> <p>(Pour les deux dernières opérations, si le motif commence avec #, il correspondra au début de la chaîne~; s'il commence avec %, il correspondra à la fin de la chaîne. Si <tt/substitut/ est vide, les occurences seront effacées.) <p>En réalité, il y en a un peu plus que ça --~des trucs comme des variables d'indirection et le parcours de tableaux~-- mais pour ça vous n'aurez qu'à étudiez le manuel vous-même <tt/:-)/. Considérez simplement ceci comme de la motivation. <p>Bon, maintenant que nous avons découvert les outils, examinons à nouveau le code~: <p><tscreen><verb> [ -z ${fn##[A-Z]*} ] </verb></tscreen> <p>Ce n'est plus <em>si</em> difficile que ça, n'est-ce pas~? Ou peut-être que si~; mon processus de pensée, lorsque j'ai affaire à des recherches et correspondances de motifs, tend à être aussi tortueux qu'un bretzel. Ce que j'ai fait ici --~et cela pourrait être fait 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 <tt/[ -z ... ]/ retourne vrai si le résultat est de longeur nulle (i.e. correspond au motif <tt/[A-Z]*/), et <tt/$MAX/ est alors mis à «~-max~». <p>Il est à noter que comme nous faisons correspondre la chaîne en entier, <tt/${fn%%[A-Z]*}/ conviendrait 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 avec tout cela. C'est facile~: donnez une valeur à un paramètre et expérimentez~: <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>... et ainsi de suite. C'est la meilleur façon de bien sentir ce que fait un outil~; prenez-le, codez-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 effectifs peuvent varier et vous surprendront souvent. <sect1>États du paramètre <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ée à une valeur ou non. On pourrait toujours vérifier sa longueur, comme j'ai fait précédemment, mais les utilitaires offerts par <tt/bash/ à cet effet nous fournissent des raccourcis utiles en de telles occasions~: (Ici, nous fairons l'hypothèse que notre variable <tt/$joe/ n'a pas été assignée, ou a une valeur nulle.) <tt/${parametre:-autre}/~: Si la variable <tt/parametre/ n'est pas assignée, <tt/autre/ est substitué.<newline> Exemple~: <tt>${joe:-mary} = mary </tt>($joe reste non-assignée). <tt/${parametre:=autre}/~: Si la variable <tt/parametre/ n'est pas assignée, assigne-lui <tt/autre/ et retourne la valeur de paramètre.<newline> Exemple~: <tt>${joe:=mary} = mary </tt>($joe="mary"). <tt/${parametre:?autre}/~: Affiche <tt/autre/ ou provoque une erreur si la variable <tt/parametre/ n'est pas assignée.<newline> Exemple~: <tscreen><verb> Odin:~$ echo ${joe:?"Not set"} bash: joe: Not set Odin:~$ echo ${joe:?} bash: joe: parameter null or not set </verb></tscreen> <tt/${parametre:+autre}/~: <tt/autre/ remplace la variable <tt/parametre/ si cette dernière est assignée.<newline> Exemple~: <tscreen><verb> Odin:~$ joe=blahblah Odin:~$ echo ${joe:+mary} mary Odin:~$ echo $joe blahblah </verb></tscreen> <sect1>Manipulation de tableaux <p> Une autre capacité interne de <tt/bash/, un mécanisme de base pour manipuler des 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érons le scénario suivant~: si j'ai un bottin d'adresse et que je veux envoyer ma dernière «~Rubrique du Marin~» à toutes les personnes de 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 tableaux deviennent rapidement une des seules options viables. Regardons ce que ça peut impliquer. Voici un extrait du bottin utilisé pour ce travail~: <tscreen><verb> Nom Catégorie Adresse e-mail Jim &ero; Fanny Delamico 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> Ouf. On voit bien que l'on doit lire ça par champs, compter les mots ne marcherait pas, ni d'aileurs une recherche de texte. Les tableaux viennent ici nous sauver~! <tscreen><verb> #!/bin/bash # 'nlmail' envoie les nouvelles mensuelles à une liste d'amis inclus # dans le bottin. # bash créerait les tableaux automatiquement, puisque nous utilisons # la syntaxe 'nom[index]' pour charger les variables. Il se trouve # toutefois que j'aime les déclarations explicites. declare -- le nom d'une catégorie d'adresse électronique # 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 ligne="$(sed -n ${x}p bottin)" # Imprime la ligne numéro "$x" nom[$x]="${line:0:25}" # Charge la variable 'nom' categorie[$x]="${line:25:10}" # etc., adresse[$x]="${line:35:25}" # etc., email[$x]="${line:60:20}" # etc. done # La suite plus loin.... </verb></tscreen> À ce point, nous avons le fichier <tt/bottin/ chargé dans quatre tableaux 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)~: <tscreen><verb> # La suite de ce qui précède... for y in $(seq $x) do # Nous ferons correspondre le mot "ami" dans le champ # 'categorie', le rendant insensible à la casse et en retirant les # caractères qui resterait. if [ -z $(echo ${categorie[$y]##[Aa]mi*}) ] then mutt -a Depeche.pdf -s 'S/V Ulysses News, 6/2000' ${email[$y]} echo "Mail envoyé à ${nom[$y]}" >> liste_envoie.txt fi done </verb></tscreen> Ça devrait faire l'affaire, comme de copier le nom des destinataires dans un fichier appelé <tt/liste_envoie.txt/ --~une chouette idée pour vérifier que personne n'a été oublié. Les possibilités de manipulation de tableaux de <tt/bash/ s'étendent un peu au-delà de ce simple exemple. Suffit-il de dire que pour des cas simples de cette sorte, avec des fichiers en-dessous de disons une centaine de ko, les tableaux <tt/bash/ sont à recommander~? Pour satisfaire à ma curiosité, j'ai créé une liste de noms qui est juste un peu plus grande que 100~ko en utilisant le bottin de l'exemple précédent~: <tscreen><verb> for n in $(seq 300); do cat bottin >> liste_nom; done </verb></tscreen> que j'ai ensuite faite tourner sur mon vieux Pentium 233/64MB. 24~secondes~; pas si mal pour 1500 enregistrements et un outil «~bricolé~». Notez que le script ci-dessous peut facilement être généralisé, par exemple, en ajoutant la possibilité de spécifier différents bottins, critères ou actions, directement sur la ligne de commande. Une fois les données divisées dans un format plus facilement adressable, les possibilités sont infinies. <sect1>Pour l'emballage... <p> <tt/bash/, en plus d'être très puissant dans son rôle d'interprète de ligne de commande et de shell, possède aussi un grand nombre d'outils sophistiqués pour tout ceux qui ont besoin de créer leurs propres programmes. À mon avis, les scripts shell correspondent parfaitement à leur créneau --~celui d'un langage de programmation simple mais puissant~-- 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, plus particulièrement, semble encourager l'attitude «~faites le vous-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 comme une meilleure intégration (et un plus grand «~quotient d'utilité~») entre la puissance du système d'exploitation et l'environnement de l'usager. «~Le pouvoir au peuple~!~»~<tt/:-)/ Au mois prochain, Linuxement ! <sect1>Citation du mois <p> <quote> «~...Aussi terrible qu'une dépendance à UNIX puisse ê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.)<newline> <newline> Vous devez à l'oracle un programme à douze étapes.~»<newline> <newline> --~L'Oracle de Usenet </quote> <sect1>Références <p> Les pages de manuel ("man") de <tt/bash/, l'aide en ligne. <p><em/«~Introduction aux scripts shell - La base~»/ par Ben Okopnik, LG #52 <p><em/«~Introduction aux scripts shell~»/ par Ben Okopnik, LG #53 <p><em/«~Introduction aux scripts shell~»/ par Ben Okopnik, LG #54 <p>Copyright 2000, Ben Okopnik. Paru dans le numéro 55 de la Linux~Gazette de Juillet 2000. <p>Traduction française de <url url="mailto:fabien@tzone.org" name="Fabien Niñoles"> <!-- ...et ici --> </article>