Avertissement :
La version originale de cet article a été publiée sur IBM developerWorks
et est la propriété de Westtech Information Services. Ce document est une
traduction de la mise à jour de la version originale de l'article
réalisée par l'équipe de documentation Gentoo et contient quelques
améliorations proposées par l'équipe de documentation de Gentoo Linux.
Ce document n'est pas activement maintenu.
|
Awk par l'exemple, 2e partie
1.
Enregistrements, boucles et tableaux
Les enregistrements sur plusieurs lignes
Awk est un excellent outil pour lire et traiter des données structurées, comme
le fichier système /etc/passwd. /etc/passwd est la
base de données UNIX des utilisateurs. C'est un fichier texte délimité par des
":", contenant beaucoup d'informations importantes, incluant entre autres tous
les comptes et les ID des utilisateurs. Dans mon article précédent, je vous ai
montré comment Awk pouvait facilement traiter les différents champs de ce
fichier. Tout ce que nous avions à faire était d'assigner ":" à la variable FS
(séparateur de champs).
En renseignant correctement la variable FS, Awk peut être configuré pour
traiter les différents champs de n'importe quelle sorte de données structurées,
tant qu'il y a un enregistrement par ligne. Cependant, FS ne sera pas suffisant
si nous voulons traiter les différents champs d'un enregistrement qui sont sur
plusieurs lignes. Dans ces situations, nous avons aussi besoin de modifier la
variable séparatrice d'enregistrements RS (Record Separator). La variable RS
indique à Awk quand l'enregistrement courant se termine et que le suivant
commence.
Par exemple, regardez comment nous faisons pour traiter une liste d'adresses de
participants du « Federal Witness Protection Program » :
Exemple de code 1.1 : Exemple de données de la liste des participants du « Federal Witness Protection Program » |
Jimmy the Weasel
100 Pleasant Drive
San Francisco, CA 12345
Big Tony
200 Incognito Ave.
Suburbia, WA 67890
|
Idéalement, nous souhaiterions qu'Awk reconnaisse chaque bloc de 3 adresses
comme étant un enregistrement individuel, plutôt que 3 enregistrements séparés.
Cela rendrait notre code un peu plus simple si Awk pouvait reconnaître la
première ligne de l'enregistrement comme le premier champ ($1), les coordonnées
de la rue comme le second champ ($2), la ville, l'état et le code postal comme
troisième champ ($3). Le code suivant va simplement faire ce que nous
voulons :
Exemple de code 1.2 : Réaliser l'initialisation d'un enregistrement de la liste d'adresse |
BEGIN {
FS="\n"
RS=""
}
|
Ci-dessus, assigner "\n" à FS indique à Awk que chaque champ apparaît sur une
ligne séparée. En assignant "" à RS, nous lui indiquons également que chaque
enregistrement d'adresse est séparé par une ligne vide. Une fois qu'Awk sait
comment les données en entrée sont formatées, il peut faire tous les
traitements d'affichage de ces enregistrements que nous souhaitons. Et le reste
du script est simple à écrire. Regardez ce script complet qui traite cette
liste d'adresse et affiche chaque enregistrement correspondant à une adresse
sur une simple ligne, séparant chaque champ avec une virgule.
Exemple de code 1.3 : Script complet |
BEGIN {
FS="\n"
RS=""
}
{ print $1 ", " $2 ", " $3 }
|
Si ce script est sauvegardé en address.awk et que les données
correspondantes aux adresses sont stockées dans un fichier appelé
address.txt, vous pouvez exécuter ce script en tapant awk -f
address.awk address.txt. Ce code produit l'affichage suivant :
Exemple de code 1.4 : Les données affichées par le script |
Jimmy the Weasel, 100 Pleasant Drive, San Francisco, CA 12345
Big Tony, 200 Incognito Ave., Suburbia, WA 67890
|
OFS et ORS
Dans la directive d'affichage « print » du fichier
address.awk, vous pouvez voir qu'Awk concatène (joint) les chaînes
de caractères qui sont les unes à côté des autres sur une ligne. Nous avons
utilisé cette fonctionnalité pour insérer une virgule et une espace (", ")
entre les trois champs d'adresse qui apparaissent sur la ligne. Bien que cette
méthode fonctionne, elle n'est pas très élégante. Plutôt que d'insérer des
", " entre nos différents champs, Awk peut le faire pour nous grâce à une
variable spéciale d'Awk appelée OFS, le séparateur des champs affichés
« Output Field Separator ». Regardez cet extrait de code :
Exemple de code 1.5 : Exemple d'un extrait de code |
print "Hello", "there", "Jim!"
|
Les virgules sur cette ligne ne font pas partie de notre chaîne de caractères
d'origine. Au lieu de cela, elles indiquent à Awk que "Hello", "there" et
"Jim!" sont des champs séparés et que la variable OFS doit être affichée entre
chaque chaîne de caractères. Par défaut, Awk produit le résultat suivant :
Exemple de code 1.6 : Affichage produit par Awk |
Hello there Jim!
|
Cela nous montre que par défaut, OFS vaut " ", une simple espace.
Cependant, nous pouvons facilement redéfinir OFS, Awk insérera alors notre
séparateur de champs favori. Voici une version modifiée de notre programme
d'origine address.awk qui utilise OFS pour afficher ces caractères
intermédiaires ", " :
Exemple de code 1.7 : Redéfinir OFS |
BEGIN {
FS="\n"
RS=""
OFS=", "
}
{ print $1, $2, $3 }
|
Awk dispose aussi une variable spéciale appelée ORS, appelée le
« séparateur des enregistrements affichés » (Ouput Record Separator).
En renseignant ORS, qui vaut par défaut le caractère de nouvelle ligne ("\n"),
nous pouvons contrôler le caractère qui sera automatiquement affiché à la fin
d'une ligne. La valeur par défaut ORS oblige Awk à afficher chaque directive
« print » sur une nouvelle ligne. Si nous voulons que les résultats
soient séparés par une double ligne, nous affecterons alors "\n\n" à ORS. Ou,
si nous voulons que les enregistrements soient séparés par une simple espace
(et non par une nouvelle ligne), nous devons mettre ORS à " ".
D'un enregistrement multiligne formaté en enregistrement avec des
tabulations
Disons que nous avons écrit un script qui convertit notre liste d'adresses vers
le format : une ligne par enregistrement en délimitant les champs par des
tabulations afin de pouvoir l'importer dans une feuille de calcul. Après avoir
utilisé une version légèrement modifiée de address.awk, cela
devient clair que notre programme ne fonctionne que pour des adresses composées
de trois lignes. Si Awk rencontre l'adresse suivante, la quatrième ligne sera
rejetée et ne sera pas affichée :
Exemple de code 1.8 : Exemple d'enregistrement |
Cousin Vinnie
Vinnie's Auto Shop
300 City Alley
Sosueme, OR 76543
|
Pour gérer de telles situations, ce serait bien si notre code prenait le nombre
de champs par enregistrements en compte, affichant chacun d'entre eux dans
l'ordre. Pour l'instant, le code affiche seulement les trois premiers champs de
l'adresse. Voici un code qui fait ce que nous voulons :
Exemple de code 1.9 : Code amélioré |
BEGIN {
FS="\n"
RS=""
ORS=""
}
{
x=1
while ( x<NF ) {
print $x "\t"
x++
}
print $NF "\n"
}
|
Tout d'abord, nous renseignons le séparateur de champs FS à "\n" et le
séparateur d'enregistrements RS à "" afin qu'Awk analyse les adresses de
plusieurs lignes correctement, comme auparavant. Alors, nous renseignons le
séparateur d'affichage des enregistrements à "", ce qui demande à la directive
« print » de ne pas afficher une nouvelle ligne à la fin de chaque
appel. Cela signifie que si nous voulons qu'un texte commence sur une nouvelle
ligne, nous avons besoin d'écrire explicitement « print "\n" ».
Dans le bloc de code principal, nous créons une variable appelée x qui conserve
le numéro du champ actuel que nous traitons. Initialement, il vaut 1. Alors,
nous utilisons une boucle « while » (une boucle d'Awk qui est
construite de la même façon que celle que l'on trouve dans le langage C) pour
tout parcourir sauf le dernier enregistrement, afficher l'enregistrement et une
nouvelle ligne ; aussi, comme qu'ORS vaut "", « print »
n'affiche plus de nouvelle ligne pour nous. Le résultat produit par le
programme est le suivant, ce qui est exactement ce que nous voulons :
Exemple de code 1.10 : Notre résultat attendu. Pas très élégant, mais délimité par des tabulations pour une importation facile dans une feuille de calcul |
Jimmy the Weasel 100 Pleasant Drive San Francisco, CA 12345
Big Tony 200 Incognito Ave. Suburbia, WA 67890
Cousin Vinnie Vinnie's Auto Shop 300 City Alley Sosueme, OR 76543
|
Fonctionnement des boucles
Nous avons déjà vu le fonctionnement des boucles « while », qui est
identique à son homologue en C. Awk a aussi une boucle « do...while »
qui évalue la condition à la fin du bloc de code plutôt qu'au début comme une
boucle « while ». C'est identique à une boucle
« repeat...until » que l'on peut trouver dans d'autres langages.
Voici un exemple :
Exemple de code 1.11 : Exemple « do...while » |
{
count=1
do {
print "Peu importe, je serais affiché au moins une fois"
} while ( count != 1 )
}
|
Parce que la condition est évaluée après le bloc de code, une boucle
« do...while », à la différence d'une boucle normale
« while », s'exécute toujours au moins une fois. Une boucle normale
« while » ne s'exécutera jamais si sa condition est fausse à la
première boucle.
Pour les boucles
Awk vous permet de créer des boucles, comme les boucles « while »
sont identiques à leur homologue en C :
Exemple de code 1.12 : Exemple de boucle |
for ( assignement initial; comparaison; incrémentation ) {
bloc de code
}
|
Voici un exemple simple :
Exemple de code 1.13 : Exemple simple |
for ( x = 1; x <= 4; x++ ) {
print "iteration",x
}
|
Cet extrait affiche :
Exemple de code 1.14 : Résultat de l'extrait de code |
iteration 1
iteration 2
iteration 3
iteration 4
|
« Break » et « continue »
De plus, exactement comme en C, Awk fournit des directives « break »
et « continue ». Ces directives fournissent un meilleur contrôle sur
les différentes implémentations de boucles d'Awk. Voici un extrait de code qui
a désespérément besoin d'une directive « break » :
Exemple de code 1.15 : Extrait de code ayant besoin d'une directive « break » |
while (1) {
print "pour toujours et toujours..."
}
|
Parce que 1 est toujours vrai, cette boucle se fera indéfiniment. Voici une
boucle qui s'exécute seulement dix fois :
Exemple de code 1.16 : Boucle qui s'exécute seulement 10 fois |
x=1
while(1) {
print "iteration",x
if ( x == 10 ) {
break
}
x++
}
|
Ici, la directive « break » est utilisée pour arrêter la boucle la
plus profonde. « break » termine donc immédiatement la boucle et
l'exécution continue à la ligne après le bloc de code de la boucle.
La directive « continue » complète « break » et fonctionne
de cette façon :
Exemple de code 1.17 : La directive « continue » complète la directive « break » |
x=1
while (1) {
if ( x == 4 ) {
x++
continue
}
print "iteration",x
if ( x > 20 ) {
break
}
x++
}
|
Ce code affichera de « iteration 1 » jusqu'à « iteration
21 », sauf « iteration 4 ». Si l'itération est égale à 4, x est
incrémenté et la directive « continue » est appelée, ayant pour effet
qu'Awk démarre alors la boucle suivante sans même exécuter le reste du bloc de
code. La directive « continue » fonctionne pour tout type de boucle
itérative d'Awk, comme le fait la directive « break ». Lorsqu'elle
est utilisée à l'intérieur d'une boucle « for », la directive
« continue » fait que la variable de contrôle de la boucle est
automatiquement incrémentée. Voici un code équivalent pour la boucle :
Exemple de code 1.18 : Code équivalent au code précédent |
for ( x=1; x<=21; x++ ) {
if ( x == 4 ) {
continue
}
print "iteration",x
}
|
Il n'est pas nécessaire d'incrémenter « x » juste avant l'appel de
« continue » comme c'était le cas dans notre boucle
« while », puisque la boucle « for » incrémente
« x » automatiquement.
Tableaux
Vous serez ravi d'apprendre qu'Awk a des tableaux. Cependant, sous Awk, il est
habituel de démarrer les index de tableau à 1, plutôt que 0 :
Exemple de code 1.19 : Exemple de tableaux Awk |
myarray[1]="jim"
myarray[2]=456
|
Quand Awk rencontre le premier assignement, myarray est créé et l'élément
myarray[1] est initialisé à "jim". Après que le second assignement ait été
évalué, le tableau a deux éléments.
Une fois défini, Awk a un moyen pratique pour parcourir les éléments d'un
tableau :
Exemple de code 1.20 : Parcourir des tableaux |
for ( x in myarray ) {
print myarray[x]
}
|
Ce code affiche chaque élément du tableau myarray. Quand vous utilisez cette
forme spéciale du « in » d'une boucle for, Awk assigne chaque index
existant de myarray à x (la variable de contrôle de la boucle) à tour de rôle,
exécutant le bloc de code de la boucle une fois après chaque assignement. Bien
que ce soit une fonctionnalité d'Awk vraiment pratique, il a un
inconvénient : quand Awk parcourt les index du tableau, il ne le fait pas
suivant un ordre particulier. Cela signifie qu'il n'y a pas de moyen pour nous
de savoir si le résultat du code précédent sera :
Exemple de code 1.21 : Résultat du code précédent |
jim
456
|
ou
Exemple de code 1.22 : Un autre résultat possible du même code |
456
jim
|
Pour reprendre une phrase de Forrest Gump, parcourir le contenu d'un tableau
est comme une boîte de chocolats : vous ne savez jamais sur quoi vous
allez tomber. Cela a quelque chose à voir avec la nature « chaînes de
caractères » des tableaux Awk, ce que nous allons voir à présent.
Les index de tableaux en « chaînes de caractères »
Dans mon article précédent, je
vous ai montré qu'Awk conserve en fait les valeurs numériques en format de
chaînes de caractères. Pendant qu'Awk réalise les conversions nécessaires pour
faire son travail, il ouvre la porte pour du code un peu étrange :
Exemple de code 1.23 : Code un peu étrange |
a="1"
b="2"
c=a+b+3
|
Une fois ce code exécuté, « c » est égal à 6. Puisqu'Awk fonctionne
avec des chaînes de caractères, ajouter les chaînes de caractères
« 1 » et 2 est fonctionnellement la même chose que d'ajouter les
nombres 1 et 2. Dans les deux cas, Awk réalisera correctement l'opération. La
nature « chaînes de caractères » est assez intrigante : vous
pouvez vous demander si nous utilisons des index de chaînes de caractères pour
les tableaux. Par exemple, prenez le code suivant :
Exemple de code 1.24 : Exemple de code |
myarr["1"]="Mr. Whipple"
print myarr["1"]
|
Comme vous pouvez vous y attendre, ce code affichera « Mr. Whipple ».
Mais que se passe-t-il si nous enlevons les guillemets autour du deuxième index
"1" ?
Exemple de code 1.25 : En enlevant les guillemets du code |
myarr["1"]="Mr. Whipple"
print myarr[1]
|
Deviner le résultat de cet extrait de code est un peu plus difficile. Est-ce
que Awk considère myarr["1"] et myarr[1] comme 2 éléments séparés du tableau,
ou référencent-ils le même élément ? La réponse est qu'ils référencent le
même élément, Awk affichera « Mr. Whipple » exactement comme dans le
premier extrait de code. Bien que cela puisse sembler étrange, derrière le
décor, Awk a utilisé des index de chaînes de caractères pour ses tableaux tout
le temps !
Après avoir pris connaissance de cette curiosité, certains d'entre nous peuvent
être tentés d'exécuter du code farfelu comme ça :
Exemple de code 1.26 : Code farfelu |
myarr["name"]="Mr. Whipple"
print myarr["name"]
|
Ce code ne génère pas d'erreur et fonctionne comme nos exemples précédents et
affichera aussi "Mr. Whipple" ! Comme vous pouvez le voir, Awk ne nous
limite pas à utiliser que des index de nombres entiers ; nous pouvons
utiliser des index de chaînes de caractères si nous le voulons, sans que cela ne
crée de problème. Chaque fois que nous utilisons des index de tableau n'étant
pas des nombres entiers comme myarr["name"], nous utilisons des tableaux
associatifs. Techniquement, Awk ne fait rien de différent quand nous utilisons
un index de chaînes de caractères (même si vous utilisez un index de
« nombre entier », Awk le traite encore comme une chaîne de
caractères). Cependant, vous devriez utiliser ces tableaux associatifs :
c'est plutôt cool et impressionnera votre patron. L'astuce d'index de chaînes
de caractères sera notre petit secret. ;)
Les outils pour les tableaux
Quand il en vient aux tableaux, Awk nous donne beaucoup de flexibilité. Nous
pouvons utiliser des chaînes de caractères comme index et nous ne sommes pas
tenus d'avoir une suite continue de nombres d'index (par exemple, nous pouvons
définir myarr[1] et myarr[1000] et laisser les autres éléments indéfinis). Bien
que cela puisse être très utile, dans certaines circonstances, cela peut porter
à confusion. Heureusement, Awk offre quelques fonctionnalités pratiques pour
aider à rendre les tableaux plus facilement manipulables.
Tout d'abord, nous pouvons supprimer des éléments d'un tableau. Si vous voulez
effacer l'élément 1 de votre tableau fooarray, tapez :
Exemple de code 1.27 : Supprimer des éléments du tableau |
delete fooarray[1]
|
Et si vous voulez voir si un élément particulier du tableau existe, vous pouvez
utiliser l'opérateur booléen spécial « in » comme décrit ci-dessous.
Exemple de code 1.28 : Vérifier si un élément particulier du tableau existe |
if ( 1 in fooarray ) {
print "Oui ! Il est ici."
} else {
print "Non ! Je ne le trouve pas."
}
|
La prochaine fois
Nous avons abordé pas mal de points dans cet article. La prochaine fois, je
compléterai votre connaissance d'Awk en vous expliquant comment utiliser les
fonctions mathématiques, les fonctions de chaînes de caractères et comment
créer vos propres fonctions. Je vous parcourrai la création d'un programme
d'équilibrage d'un carnet de chèques. D'ici là, je vous encourage à écrire
quelques programmes Awk et de consulter les ressources suivantes.
2.
Ressources
|