Introduction à l'écriture de scripts shell — Partie 4

Gazette Linux n°114 — Mai 2005

Joëlle Cornavin

Adaptation française  

Damien Bosq

Relecture de la version française  

Article paru dans le n°114 de la Gazette Linux de mai 2005.

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

1. Expansion des paramètres
2. État des paramètres
3. Gestion des tableaux
4. Récapitulation
5. Références

1. Expansion des paramètres

Les fonctions d'analyse intégrées de bash sont plutôt minimales comparées à Perl ou AWK ; à mon avis, elles ne sont pas prévues pour un traitement sérieux, juste une « gestion rapide et sommaire »; des tâches mineures. Néanmoins, elles peuvent être très pratiques dans ce but.

À titre d'exemple, supposons que vous deviez faire la différence entre des noms de fichiers en minuscules et en majuscules lors du traitement d'un répertoire. J'ai fini par le faire avec mes fonds d'écran pour X, du fait que certains d'entre eux apparaissaient au mieux en mosaïque et les autres étaient étirés de façon à s'adapter à la taille de l'écran (la taille du fichier n'était pas exactement un bon guide). J'ai donc mis en majuscules tous les noms d'images en mode plein écran et en minuscules toutes les mosaïques. Alors, comme faisant partie de mon sélecteur de fond d'écran aléatoire, bkgr, j'ai écrit le script suivant :

# We need _just_ the filename
fn=$(basename $fnm)

# Set the "-max" switch if the test returns 'true'
[ -z "${fn##[A-Z]*}" ] && MAX="-max"

# Run "xv" with|without the "-max" option based on the test result
xv -root -quit $MAX $fnm &

Ces lignes sont un peu confuses, n'est-ce pas ? Bien, une partie que nous connaissons déjà : la ligne [ -z ... ] est un test pour une chaîne de longueur 0. Qu'en est-il de l'autre partie, cependant ?

Afin de protéger le résultat de notre expansion de paramètres de ce monde froid et cruel (par exemple, si vous voulez utiliser le résultat comme élément d'un nom de fichier, vous avez besoin de la « protection »; pour le garder séparé des caractères de fin), nous utilisons des accolades pour entourer l'expression entière :

$n est identique à ${n} si ce n'est que la seconde expression peut se combiner avec d'autres élements sans perdre son identité, de la manière suivante :

...
d=Digit

# "Digitize"
echo ${d}ize

# "Digital"
echo ${d}al

# "Digits"
echo ${d}s

# "Digitalis"
echo ${d}alis
...

Maintenant que nous avons séparé la variable par les accolades, nous pouvons appliquer quelques outils incorporés dans Bash pour effectuer une analyse quelque peu rudimentaire. Notez que j'en afficherai le résultat après chaque instruction echo comme si cette instruction avait été exécutée.

### Let's pick a nice, longish word to play with.
var="amanuensis"

### ${#var} - return length
echo ${#var}
10

### ${var#word} - cut shortest match from start
echo ${var#*n}
uensis

### ${var##word} - cut longest match from start
echo ${var##*n}
sis

### ${var%word} - cut shortest match from end
echo ${var%n*}
amanue

### ${var%%word} - cut longest match from end
echo ${var%%n*}
ama

### ${var:offset} - return string starting at 'offset'
echo ${var:7}
sis

### ${var:offset:length} - return 'length' characters starting at 'offset'
echo ${var:1:3}
man

### ${var/pattern/string} - replace single match
echo ${var/amanuen/paralip}
paralipsis

### ${var//pattern/string} - replace all matches
echo ${var//a/A}
AmAnuensis

(Pour les deux dernières opérations, si le motif commence par #, il correspond au début de la chaîne ; s'il commence par %, il correspond à la fin. Si la chaîne est vide, les correspondances sont supprimées.)

En fait, il y a autre chose, l'indirection de variable et l'analyse de tableaux, mais je pense que devrez étudier la page de man vous-même. Considérez cela comme un exercice de motivation.

Maintenant que nous avons examiné les outils, revenons au code : [ -z "${fn##[A-Z]*}" ]. Aucune difficulté ? Si, peut-être : mon processus de pensée, en abordant les recherches et correspondances a tendance aux complications. Ce que j'ai fait ici aurait pu l'être via un certain nombre d'autres méthodes, étant donné les outils ci-dessus ‐ est une correspondance pour une chaîne de longueur maximale (c'est-à-dire, le nom de fichier entier) qui commence par un caractère en majuscule. Le [ -z ... ] retourne true si la chaîne résultante est de longueur zéro (c'est-à-dire correspond au motif [A-Z]*) et $MAX est défini à -max.

Notez que, puisque nous correspondons à la chaîne entière, ${fn%%[A-Z]*} fonctionnerait parfaitement aussi. Si cela semble confus, comme tout ce qui précède, je vous suggère des quantités d'expérimentations pour vous familiariser avec ce concept. C'est facile : définissez un paramètre de valeur et testez, de la manière suivante :

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 meilleure façon de vous faire une idée de ce que fait un outil ; sélectionnez-le, essayez-le. Respectez toutes les précautions car une suppression aléatoire de données risque de se produire. Les résultats réels peuvent varier et vous surprendront souvent.

2. État des paramètres

Il peut arriver de tester une plage à la recherche de conditions d'erreur qui définissent différentes variables, quand il s'agit de savoir si une variable donnée est définie (s'est vue affecter une valeur ou non). En fait, nous pourrions tester la longueur, comme je l'ai fait ci-dessus, mais les utilitaires que bash fournit pour ce faire offrent des raccourcis pratiques pour ce genre d'opérations.

joe=

### ${parameter:-word} - If parameter is unset, substitute "word"
echo ${joe:-mary}
mary
echo $joe

### ${parameter:?word} - Display "word" or error if parameter is unset
echo ${joe:?"Not set"}
bash: joe: Not set
echo ${joe:?}
bash: joe: parameter null or not set

### ${parameter:=word} - If parameter is unset, set it to "word"
echo ${joe:=mary}
mary
echo $joe
mary

### ${parameter:+word} - "word" is substituted only if parameter is set
echo ${joe:+blahblah}
blahblah

3. Gestion des tableaux

Une autre fonction intégrée de bash, un mécanisme de base pour gérer les tableaux nous permet de traiter les données à indexer ou au moins, conservées dans une structure qui autorise un adressage individuel de ses membres. Imaginez le scénario suivant : j'ai un agenda téléphonique ou un carnet d'adresses et je souhaite envoyer ma toute dernière Sailor's Newsletter à tout le monde dans la catégorie Amis, comment faire ? Supposons en outre que je veuille aussi créer une liste des noms des personnes auxquels je l'ai envoyée ou toute autre opération... il devient nécessaire de la scinder en champs par longueur. Les tableaux deviennent une des options tout à fait viables.

Observons ce que cela pourrait impliquer. Voici un clip d'un agenda téléphonique à utiliser pour ce travail :

NomCatégorieAdresseAdresse électronique
Jean et Annick DupontAmis2, rue des Vignes 75016 Parisjdupont@toto.com
Frédéric MartinAmi12, impasse de la Grotte 06000 Nicemartin@ici.com
Louise FrançoisCollègue127, allée des Acacias 45000 Orléanslfran@fai.fr
Marc LebihanFamilleLes Chénes verts 12000 Rodezlebihan@chezmoi.fr
Les Amis de LinuxAssociation2, boulevard Victor Hugo 89000 Sensamis-linux.org

Il faut bien sûr lire tout cela par champs, ce que le compteur de mots ni un une recherche de texte ne peuvent faire. Les tableaux viennent à notre secours !

#!/bin/bash
# 'nlmail' sends the monthly newsletter to friends listed
# in the phonebook

# "bash" would create the arrays automatically, since we'll
# use the 'name[subscript]' syntax to load the variables -
# but I happen to like explicit declarations.

declare -a name category address email

# A little Deep Hackery to make the 'for' loop read a line at a time
OLD=$IFS
IFS='
'
for line in $(cat phonelist)
do
    # Increment the line counter
    ((x++))

    # Load up the 'name' variable
    name[$x]="${line:0:25}"
    
    # etc. for 'category',
    category[$x]="${line:25:10}"
    
    # etc. for 'address',
    address[$x]="${line:35:25}"
    
    # etc. for 'email'...
    email[$x]="${line:60:20}"
done

# Undo the line-parsing magic
IFS=$OLD

...

À ce stade, le fichier phonelist est chargé dans les quatre tableaux que nous avons créés, prêts pour un autre traitement. Chacun des champs est aisément adressable, ce qui rend trivial le problème posé — consistant à envoyer par courrier électronique un message donné à tous mes amis —  (cet extrait est la continuation du script précédent) :

...
for y in $(seq $x)
do
    # We'll look for the word "friend" in the 'category' field,
    # make it "case-blind", and clip any trailing characters.
    if [ -z "${category[$y]##[Ff]riend*}" ]
    then
        mutt -a Newsletter.pdf -s 'S/V Ulysses News, 6/2000' ${email[$y]}
        echo "Mail sent to ${name[$y]}" >> sent_list.txt
    fi
done

Cela devrait fonctionner, ainsi que le collage des noms des destinataires dans un fichier appelé sent_list.txt, une fonctionnalité de double contrôle utile qui me permet de vérifier que je n'ai oublié personne.

Les fonctions de traitement des tableaux de bash s'étendent un peu au-delà de cet exemple simple. On se bornera à dire que pour les cas simples de ce genre, avec par exemple des fichiers de moins de 200 ko, les tableaux bash sont la solution.

Notez que le script ci-dessus peut être aisément généralisé : à titre d'exemple, vous pourriez ajouter la capacité de spécifier différentes listes de numéros de téléphone, critères ou actions, précisément depuis la ligne de commande. Une fois que les données sont scindées en un format facilement adressable, les possibilités sont infinies...

4. Récapitulation

Bash, bien que très doué dans son rôle d'interpréteur/shell en ligne de commande, se glorifie d'avoir un grand nombre d'outils plutôt sophistiqués à disposition de quiconque doit créer des programmes personnalisés. À mon avis, l'écritre de scripts shell suit son chemin, celui d'un langage de programmation simple mais puissant — parfaitement, trouvant sa place entre l'utilitaire en ligne de commande et la programmation (C, Tcl/Tk, Perl, Python) au sens propre du terme et devrait faire partie de l'arsenal de tout utilisateur *nix. Linux encourage l'approche du «  faites-le vous-même »; parmi ses utilisateurs en leur donnant accès à des outils puissants et le moyen d'en automatiser l'emploi — ce que je considère comme une intégration plus rigoureuse (et partant, un « quotient de rentabilité » beaucoup plus élevé) entre la puissance sous-jacente du système d'exploitation et l'environnement utilisateur.

5. Références

Les pages de manuel de bash, builtins, sed, mutt.

Ben Okpnik est le rédacteur en chef de la Linux Gazette© et il est membre de l'« Answer Gang© ».

Né à Moscou (Russie) en 1962, Ben a commencé à s'intéresser à l'électricité à l'âge de six ans. Il l'a démontré rapidement en plantant une fourchette dans une prise électrique, déclenchant ainsi un début d'incendie et depuis lors, il est plongé dans le dédale de la technologie. Il travaille sur les ordinateurs depuis le tout début, lorsqu'il fallait les construire en soudant des composants sur des circuits imprimés et que les programmes devaient tenir dans 4 ko de mémoire. Il paierait chèrement tout psychologue susceptible de le guérir des affreux cauchemars qu'il en a gardés.

Ses expériences ultérieures comprennent la création de logiciels dans près de douze langages, la maintenance de réseaux et de bases de données à l'approche d'un ouragan, la rédaction d'articles pour des publications allant des magazines consacrés à la voile aux revues sur la technologie. Après une croisière en voilier de sept ans sur l'Atlantique et la mer des Caraïbes ponctuée de quelques escales sur la côte est des États-Unis, il a pour l'instant jeté l'ancre à St Augustine (Floride). Il est instructeur technique chez Sun Microsystems© et travaille également à titre privé comme consultant open source/développeur web. Ses passe-temps actuels sont notamment l'aviation, le yoga, les arts martiaux, la moto, l'écriture et l'histoire romaine. Son Palm Pilot© est truffé d'alarmes dont la plupart contiennent des points d'exclamation.

Ben travaille avec Linux depuis 1997 et le crédite de sa perte complète d'intérêt pour les campagnes de guerre nucléaire sur les territoires nord-ouest du Pacifique.

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.