Gentoo Logo

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

Table des matières :

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



Imprimer

Dernière mise à jour le 31 octobre 2005

Résumé : Suite à son introduction à Awk, dans cette séquence, Daniel Robbins continue l'exploration d'Awk, le grand langage au nom étrange. Daniel va vous expliquer comment manipuler des enregistrements sur plusieurs lignes, utiliser les boucles et créer et utiliser des tableaux Awk. À la fin de cet article, vous aurez déjà vu une bonne partie des fonctionnalités d'Awk et vous serez prêt à écrire vos propres scripts avancés en Awk.

Daniel Robbins
Auteur

Christophe Lefebvre
Traducteur

Donate to support our development efforts.

Copyright 2001-2014 Gentoo Foundation, Inc. Questions, Comments? Contact us.