Les Secrets sombres et cachés de Bash

Gazette Linux n°55 — Juillet 2000

Fabien Niñoles

Adaptation française 

Frédéric Marchal

Correction du DocBook 

Article paru dans le n°55 de la Gazette Linux de juillet 2000.

Cet article est publié selon les termes de la Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.


Table des matières

Expensions des paramètres
États du paramètre
Manipulation de vecteurs
Pour l'emballage...
Citation du mois
Références
 

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.

 
 -- Jeremy Anderson

Dans les profondeurs de la page de manuel de bash> 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 « 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 hors du sujet)

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 ! SOURIEZ

Expensions des paramètres

Les capacités internes de l'analyseur syntaxique de bash sont plutôt minimale comparées à, par exemple, perl ou awk : 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.

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, bkgr, j'ai mis le code suivant :

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.

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 ?

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. $d est équivalent à ${d} excepté que la deuxième espèce peut être combinée à d'autres choses sans perdre son identité — comme dans :

d=Ros
echo ${d}i        # "Rosi"
echo ${d}e        # "Rose"
echo ${d}a        # "Rosa"
echo ${d}alis     # "Rosalis"

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 à bash (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") :

${#parametre} — retourne la longueur de la valeur du paramètre.

EXAMPLE: ${#parametre} = 10

${parametre#motif} — retire la plus petite correspondance à partir du début du paramètre.

EXAMPLE: ${parametre#*n} = uensis

${parametre##motif} — retire la plus grande correspondance à partir du début du paramètre.

EXAMPLE: ${parametre#*n} = sis

${parametre%motif} — retire la plus petite correspondance à partir de la fin du paramètre

EXAMPLE: ${parametre%n*} = amanue

${parametre%%motif} — retire la plus grande correspondance à partir de la fin du paramètre

EXAMPLE: ${parametre%%n*} = ama

${parametre:decalage} — retourne la valeur du paramètre débutant à « decalage ».

EXAMPLE: >${parametre:7} = sis>

${parametre:decalage:longueur} — retourne « lougueur » caractères commençant à « decalage ».

EXAMPLE: ${parametre:1:3} = man

${parametre/motif/substitut} — remplace la première correspondance.

EXAMPLE: ${parametre/amanuen/paralip} = paralipsis

${parametre//motif/substitut} —remplace toutes les correspondances.

EXAMPLE: ${parametre//a/A} = AmAnuensis

(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.)

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. SOURIEZ considérez simplement ceci comme du matériel de motivation.

Bon, maintenant que nous avons découvert les outils, examinons à nouveau le code —

[ -z ${fn##[A-Z]*} ]

Pas si 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 [ -z ... ] retourne vrai si le résultat est nul (i.e. correspond au motif [A-Z]*), et $MAX est alors mis à « -max ».

Il est à noter que comme nous faisons correspondre la chaîne en entier, ${fn%%[A-Z]*} marcherait tout autant. Si cela vous semble confus — si tout 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 —

Odin:~$ experiment=supercallifragilisticexpialadocious
Odin:~$ echo ${experiment%l*}
supercallifragilisticexpia
Odin:~$ echo ${experiment%%l*}
superca
Odin:~$ echo ${experiment#*l}
lifragilisticexpialadocious
Odin:~$ echo ${experiment##*l}
adocious

... 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.

États du paramètre

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 bash à cet effet nous fournissent des raccourcis utiles pour de telles occasions :

(Ici, nous assumerons que notre variable — $joe — n'a pas été assignée ou a une valeur nulle.)

${parametre:-autre} — Si parametre n'est pas assignée, « autre » est substitué.

EXAMPLE: ${joe:-mary} = mary ($joe reste non-assignée.)

${parametre:=autre} — Si parametre n'est pas assignée, assigne-lui « autre » et retourne la valeur de paramètre.

EXAMPLE: ${joe:=mary} = mary ($joe="mary".)

${parametre:?autre} — Affiche « autre » ou provoque une erreur si parametre n'est pas assignée.

EXAMPLE:

Odin:~$ echo ${joe:?"Not set"}
bash: joe: Not set
Odin:~$ echo ${joe:?}
bash: joe: parameter null or not set

${parametre:+autre}« autre » remplace parametre si parametre est assignée.

EXAMPLE:

Odin:~$ joe=blahblah
Odin:~$ echo ${joe:+mary}
mary
Odin:~$ echo $joe
blahblah

Manipulation de vecteurs

Une autre capacité interne de bash, 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.

Regardons ce que ça peut impliquer. Voici un extrait du bottin utilisé pour ce travail :

Nom                  Catégorie Adresse                  e-mail
Jim & Fanny Friends      Affaires  101101 Digital Dr. LA CA fr@gnarly.com
Fred & 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, & Myndi    Affaires  5-X Rated St. Holiday FL 3cuties@fl.net

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 !

#!/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....

À 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) :

# 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

Ç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.

Les capacités de manipulation de vecteurs de bash 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 bash 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 :

for n in $(seq 300); do cat bottin >> liste_nom; done

que j'aie ensuite roulé sur mon vieux Pentium 233/64MB. 24 secondes ; pas si pire pour 1500 enregistrements et un outil « bricolé ».

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.

Pour l'emballage...

bash, 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 ! » SOURIEZ

Until next month - Happy Linuxing!

Citation du mois

 

...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.)

You owe the Oracle a twelve-step program.

 
 -- The Usenet Oracle

Références

Les pages de manuel ("man") de bash, l'aide en ligne.

Adaptation française de la Gazette Linux

L'adaptation française de ce document a été réalisée dans le cadre du Projet de traduction de la Gazette Linux.

Vous pourrez lire d'autres articles traduits et en apprendre plus sur ce projet en visitant notre site : http://www.traduc.org/Gazette_Linux.

Si vous souhaitez apporter votre contribution, n'hésitez pas à nous rejoindre, nous serons heureux de vous accueillir.