La commande awk


Nous savons maintenant comment :

Nous allons maintenant aborder la commande awk qui va être utilisée pour traiter des fichiers contenant des tableaux.


Les commandes de base


Dans une grande partie de ce chapitre, nous travaillerons à partir de l'exemple suivant :

bash$ cat exemple_awk
Mathematiques   100       50        73
Physique        97        43        121
Economie        89        23        65
Japonais        17        7         3

La première commande awk que nous rencontrerons est la suivante :

bash$ awk '{print $1}' exemple_awk
Mathematiques
Physique
Economie
Japonais

Par '$1', on désigne la première colonne du tableau, et grâce à la commande print, on affiche cette colonne.

On notera que les actions awk sont toujours encadrées par des accolades '{...}'.

D'une manière générale, awk pourra effectuer des tests sur les valeurs de la manière suivante :

bash$ awk '$2>20 {print $1}' exemple_awk
Mathematiques
Physique
Economie

Seules les lignes dont la deuxième colonne est supérieure à 20 subiront l'action.

Formattage sophistiqué.

Comme nous l'avons vu, les valeurs des différentes colonnes d'un tableau peuvent être affichées en utilisant tout simplement la commande print de awk, et le numéro de la colonne est passé en utilisant le symbole '$'.

Il est également possible d'afficher plusieurs colonnes :

bash$ awk '{print $1,$2}' exemple_awk
Mathematiques 100
Physique 97
Economie 89
Japonais 17

mais aussi d'insérer explicitement des chaînes dans cet affichage :

bash$ awk '{print $1 , ":" , $2}' exemple_awk
Mathematiques : 100
Physique : 97
Economie : 89
Japonais : 17

Enfin, on notera que le joker '$0' désigne toutes les colonnes :

bash$ awk '{print $0}' exemple_awk
Mathematiques   100    50     73
Physique        97     43     121
Economie        89     23     65
Japonais        17     7      3

Cependant, on pourra également utiliser la commande plus raffinée printf (qui est en fait celle du C) : nous ne citerons ici que quelques-unes de ses utilisations, et le lecteur pourra se reporter à Unix tools for text processing.

D'une manière générale, la commande printf prend en entrée :

La façon la plus simple de présenter sa syntaxe est certainement de présenter un exemple :

bash$ awk '{printf "%s : %d\n", $1 , $2}' exemple_awk
Mathematiques : 100
Physique : 97
Economie : 89
Japonais : 17

Cela peut paraître bien barbare, mais disséquons cet exemple : la chaîne "%s : %d\n" spécifie à printf :

Aux entrées annoncées à printf vont alors correspondre $1 et $2 : la première et la deuxième colonne de notre tableau.

D'une manière générale, on annonce des données à printf en utilisant le symbole '%' suivi du type de la donnée :

  • 'd' pour entiers
  • 'f' pour flottant (chiffres à virgule)
  • 's' pour chaîne de caractères
  • Le but de toutes ces options est de pouvoir régler de manière précise l'affichage des données (et d'obtenir, entre autre, un bon alignement des caractères - ce qui n'était pas vrai avec print). Quelques exemples :

    bash$ awk '{printf "%15s : %3d\n", $1 , $2}' exemple_awk
      Mathematiques : 100
           Physique :  97
           Economie :  89
           Japonais :  17

    ou encore :

    bash$ awk '{printf "%-9.5s : %4d\n" , $1 , $2}' exemple_awk
    Mathe     :  100
    Physi     :   97
    Econo     :   89
    Japon     :   17

    Ecrire des scripts.

    Comme c'était déjà le cas pour le shell et sed, il est possible de regrouper toutes les commandes que l'on souhaite passer à awk dans un seul fichier appelé script. Un tel fichier peut se présenter essentiellement sous deux formes :


    Les tests awk


    Comme nous l'avons déjà entrevu, il est possible de n'exécuter une action qu'à la condition qu'un certain test soit vrai. C'est ces tests sur lesquels nous allons maintenant nous pencher.

    Comparaisons numériques.

    Etant données deux colonnes contenant des chiffres, il est possible d'effectuer différents tests de comparaison :

    ==
       égalité
    >
       strictement supérieur
    >=
       supérieur ou égal
    !=
       différent
    <
       strictement inférieur
    <=
       inférieur ou égal

     

    Par exemple :

    bash$ awk '$2>40 {printf "%15s : %4d - Matching\n" , $1 , $2}' exemple_awk
      Mathematiques :  100 - Matching
           Physique :   97 - Matching
           Economie :   89 - Matching

    Comparaisons de chaînes de caractères.

    De même, étant données des colonnes contenant des chaînes de caractères, les tests suivants peuvent être effectués :

    ==
       égalité
    $n~/regexp/
       vérifie si $n correspond au motif regexp
    /regexp/
       vérifie si la ligne courante correspond au motif

    Donnons maintenant quelques exemples :

    bash$ awk '$1=="Japonais" {print $1 " contient " $2 " livres"}' exemple_awk
    Japonais contient 17 livres

    L'utilisation des expression régulières est très similaire à celle qui a été faite dans grep et sed :

    bash$ awk '$1~/^[MJ]/ {print $1 " : " $2}' exemple_awk
    Mathematiques : 100
    Japonais : 17

    On recherche toutes les entrées dont le premier champ commence par un 'M' ou un 'J' (expression '^[MJ]').

    Quels sont maintenant les lignes contenant la chaîne 'ique' :

    bash$ awk '$0~/ique/ {print $0}' exemple_awk
    Mathematiques   100     50     73
    Physique        97      43     121

    Enfin, les lignes contenant un mot se terminant par 'ique' :

    bash$ awk '$0~/ique\>/ {print $0}' exemple_awk
    Physique     97     43     121

    Rechercher certaines lignes.

    awk maintient une variable spéciale "NR" (pour Number of Record) contenant le numéro de ligne de l'entrée courante. Afin de rechercher une ligne particulière, il suffit d'effectuer un test sur cette variable NR.

    bash$ awk 'NR==2 {print $1}' exemple_awk
    Physique

    bash$ awk 'NR>=2 {print $1}' exemple_awk
    Physique
    Economie
    Japonais

    On notera également que awk possède deux descripteurs de lignes particuliers : "BEGIN" et "END" permettant d'insérer des commentaires respectivement avant et après les sorties awk :

    bash$ ls -la awk_script3
    -rwxr-xr-x 1 bac bac 211 Mar 5 09:03 awk_script3*

    bash$ cat awk_script3
    #! /bin/awk -f

    BEGIN    {printf "%15s %15s %15s\n" , "Discipline" , "Nbr de livres" , "Nbr de revues" }
      NR>=2  {printf "%15s %15d %15d\n" , $1, $2, $3}
    END      {print "*************** FIN DE FICHIER ***************"}


    bash$ ./awk_script3 exemple_awk
    Discipline     Nbr de livres  Nbr de revues
    Physique                  97             43
    Economie                  89             23
    Japonais                  17              7
    *************** FIN DE FICHIER ***************

    L'interêt d'utiliser printf plutôt que print dans cet exemple est d'aligner toutes les sorties (c'est-à-dire, la ligne "d'introduction" BEGIN, puis les résultats de awk). Par curiosité, on pourra essayer de remplacer les printf par des print ...

    Enfin, on notera qu'il est possible de spécifier un intervalle de lignes en donnant deux bornes séparées par une virgule :

    bash$ awk 'NR==1,NR==3 {print $1}' exemple_awk
    Mathematiques
    Physique
    Economie

    Les lignes concernées ici sont celles comprises entre la première et la troisième.

    Combiner les tests.

    Il est possible dans awk de combiner les tests précédents en utilisant les opérateurs logiques :

    &&
       et
    ||
       ou
    !
       non

    Par exemple :

    bash$ awk '$1~/ique/ && $2 >= 100 {print $1, $2}' exemple_awk
    Mathematiques 100

    L'expression précédente permet de rechercher toutes les lignes dont le premier champ contient la chaîne de caractères 'ique' et dont le deuxième champ est supérieur à 100.

    bash$ awk '$1~/ais\>/ || $2 >= 100 {print $1, $2}' exemple_awk
    Mathematiques 100
    Japonais 17

    On a recherché toutes les lignes dont le premier champ contient un mot se terminant par 'ais' ou dont le deuxième champ est supérieur à 100.


    Définir des variables


    Lorsque l'on manipule des tableaux, on peut être intéressés par :

    La manière de définir un variable en awk est très simple : il suffit de l'utiliser (et elle est alors initialisée automatiquement à 0) :

    bash$ ls -la awk_script4
    -rwxr-xr-x 1 bac bac 81 Mar 5 09:40 awk_script4*

    bash$ cat awk_script4
    #! /bin/awk -f

           {sum = sum + $2}
    END    {print "Nombre total de livres : " sum}

    bash$ ./awk_script4 exemple_awk
    Nombre total de livres : 303

    La première fois que la variable sum est rencontrée par awk, elle est initialisée à 0, puis pour chaque ligne, elle est incrémentée de la valeur contenue dans $2.

    Afin d'avoir des exemples plus éloquents, considérons l'exemple (légèrement enrichi) suivant :

    bash$ cat exemple_awk2
    Sciences   Mathematiques   100    50    73
    Sciences   Physique        97     43    121
    Lettres    Economie        89     23    65
    Lettres    Japonais        17     7     3

    On a par exemple :

    bash$ cat awk_script5
    #! /bin/awk -f

    $1=="Sciences" {suml = suml + $3 ; sumr = sumr + $4}
    END {printf "Nombre total de livres scientifiques : %d\n", suml
    printf "Nombre total de revues scientifiques : %d\n" , sumr}

    bash$ ./awk_script5 exemple_awk2
    Nombre total de livres scientifiques : 197
    Nombre total de revues scientifiques : 93

    On notera qu'il est possible d'effectuer plusieurs opérations successives en les séparant par un ';' (comme dans 'suml = suml + $3 ; sumr = sumr + $4').

    Nous terminerons cette section par l'exemple plus "consistant" suivant :

    bash$ cat awk_script6
    #! /bin/awk -f
    BEGIN {
          printf "%-15s %-15s %-15s %-15s %-15s\n","Discipline", "Nr livres", "Nr revues", "Moyenne", "Secteur"
          }
    $1=="Sciences" {
                   printf "%-15s %-15d %-15d %-15d %-15s\n", $2, $3, $4, ($2+$3)/2, $1
                   sumsl = sumsl + $3 ; sumsr = sumsr + $4 ; nbrs = nbrs + 1
                   }
    $1=="Lettres"  {
                   printf "%-15s %-15d %-15d %-15d %-15s\n", $2, $3, $4, ($2+$3)/2, $1
                   sumll = sumll + $3 ; sumlr = sumlr + $4 ; nbrl = nbrl + 1
                   }
    END   {
          printf "---------------------------------------------------\n"
          printf "%-15s %-15d %-15d\n", "Total Sc :", sumsl, sumsr
          printf "%-15s %-15d %-15d\n", "Total Le :", sumll, sumlr
          printf "%-15s %-15d %-15d %-15d\n", "Moy Sc :", sumsl/nbrs,sumsr/nbrs,(sumsl+sumsr)/(2*nbrs)
          printf "%-15s %-15d %-15d %-15d\n", "Moy Le :", sumll/nbrl,sumlr/nbrl,(sumll+sumlr)/(2*nbrl)
          }

    bash$ ./awk_script6 exemple_awk2
    Discipline     Nr livres      Nr revues      Moyenne        Secteur
    Mathematiques  100            50             50             Sciences
    Physique       97             43             48             Sciences
    Economie       89             23             44             Lettres
    Japonais       17             7              8              Lettres
    ---------------------------------------------------
    Total Sc :     197            93
    Total Le :     106            30
    Moy Sc :       98             46             72
    Moy Le :       53             15             34

    Dans cet exemple, on calcule le nombre moyen de livres et de revues par secteur (respectivement sciences et lettres). Les variables :

    Remarque importante :

    Si BEGIN, END ou un test sont suivis de plusieurs actions, on encardre ces dernières par des accolades '{...}'. L'accolade ouvrante '{' doit TOUJOURS être sur la même ligne que le BEGIN, END ou le test.

    Syntaxe correcte :

    BEGIN {
          commande 1
          commande 2

          }
    NR==2 {
          commande 3
          commande 4
          }

    Syntaxe incorrecte :

    BEGIN
          {
          commande 1
          commande 2

          }
    NR==2
          {
          commande 3
          commande 4
          }


    La suite ...


    On notera seulement ici qu'il est également possible :

    Pour toutes ces possibilités, le lecteur se reportera à Unix tools for text processing où toutes ces fonctionnalités sont amplement traitées.

    Mentionnons cependant un petit exemple utilisant la commande if :

    {if (NR>2 && $1~/\<[A-Z]/) print "Good"}

    Récapitulatif :

    if (condition) action
    if (condition) action else action
    do action while (condition)
    for (expr1;expr2;expr3) action
    for (var in array) action

     

    Exercice 8