4. Eléments d'initiation à  awk

awk est un langage de script particulièrement intéressant dans l'analyse et le traitement de fichier texte ainsi que dans la génération de rapports. Nous présentons dans cette partie ces principales caractéristiques sur des exemples.

4.1. Premier exemple

On suppose vouloir extraire du fichier /etc/passwd les uids, gids et nom d'utilisateur, le tout trié en majeur sur l'uid et en mineur sur le gid. Bien entendu, les lignes de commentaires ne doivent pas apparaître. Voilà  typiquement un travail pour awk.

EXEMPLE :

$ awk -F":" '$0 !~ /^#/ { print $3"\t"$4"\t"$1 }' /etc/passwd | sort -n -k 1,2  
0       0       root
0       0       toor
1       1       daemon
2       5       operator
3       7       bin
4       65533   tty
5       65533   kmem
7       13      games
8       8       news
9       9       man
22      22      sshd
25      25      smmsp
26      26      mailnull
53      53      bind
66      66      uucp
67      67      xten
68      6       pop
70      70      pgsql
80      80      www
88      88      mysql
666     666     pascal
65534   65534   nobody

L'option -F de l'interpréteur awk permet de spécifier le séparateur de champ, ici le caractère : . Le reste dit que si la ligne dénotée par $0 ne commence pas par le caractère # (c'est la syntaxe de csh, i.e. du langage C) alors on imprime sur la sortie standard les trois champs dénotés $3, $4 puis $1 (désignant respectivement l'uid, le gid et le nom d'utilisateur), lesquels sont repris en entrée de la commande sort qui trie numériquement (option -n) respectivement sur les champs positionnels 1 et 2 (i.e. l'uid et le gid). Noter enfin, l'utilisation des quotes qui permet d'empêcher l'évaluation des variables $0, $1, $3 et $4 par le shell.

L'utilisation de awk ne se limite pas aux scripts à  une ligne (les fameux one-liners), c'est un véritable langage de programmation doté de structures de contrôles et manipulant des variables.

4.2. Scripts externes

Si le code du script awk devient complexe et/ou s'étend sur plusieurs lignes, il est possible de le mettre dans un fichier indépendant. Pour l'invoquer ensuite il suffit de passer l'option -f à  l'interpréteur, comme suit :

EXEMPLE :

$ awk -f myscript.awk <fichier_à _traiter>                                       

Blocs d'instructions

La structure typique d'un script awk est composé de trois blocs :

STRUCTURE D'UN SCRIPT awk :

BEGIN {
  # bloc spécial d'initialisation, facultatif, exécuté avant que awk ne commence
  # à  traiter le fichier d'entrée <fichier_à _traiter> 
}
{
  # bloc de code exécuté à  chaque ligne du fichier <fichier_à _traiter> 
}
END {
  # bloc spécial de terminaison, facultatif, exécuté après que awk ait terminé
  # le traitement du fichier d'entrée <fichier_à _traiter> 
}

Toute ce qui suit le caractère # est considéré comme un commentaire qui est ignoré par l'interprète awk.

Si nous reprenons sous forme de script externe, le one-liners précédent (c.f. Section 4.1, « Premier exemple »), cela peut donner :

CODE :

# ~/bin/myscript1.awk                                                           

BEGIN {
# block d'initialisation                                                
  FS=":"     # Field Separator        : positionné à  ":"
  OFS="\t"   # Output Field Separator : positionné à  "\t"
}
{
# block d'execution
  if ( $0 !~ /^#/ ) {
    print $3,$4,$1
  }
}
END {
# block final
}

UTILISATION :

$ awk -f ~/bin/myscript1.awk /etc/passwd | sort -n -k 1,2

Exemple 2. Autre exemple, trouver l'uid maximum dans le fichier /etc/passwd et l'afficher ainsi que le nom de login et le gid du groupe correspondant.

CODE :

# ~/bin/myscript2.awk                                                           

BEGIN {
# block d'initialisation                                    
  FS=":"     # Field Separator        : positionné à  ":"
  OFS="\t"   # Output Field Separator : positionné à  "\t"
  uid=0
  gid=0
  name=""
}
{
# block d'execution - recherche de l'uid et du gid maximun
  if ( $0 !~ /^#/ ) {
       if ( $3 > uid ) {
 uid=$3
 gid=$4
 name=$1
       }
  }
}
END {
# block final
  print "nom = ", name, "uid max = ", uid, "gid = ", gid 
}

UTILISATION :

$ awk -f ~/bin/myscript2.awk /etc/passwd 
nom =   nobody  uid max =       65534   gid =   65534

Example 3. La commande ps aux permet sur un système BSD d'obtenir par processus, entre autre, la taille de la mémoire virtuelle utilisée (champs vsz) ainsi que la taille de la mémoire réelle utilisée (champs rss), l'unité étant le kilo-octet. On désire avoir le total pour ces deux paramètres tous processus confondus.

CODE :

# ~/bin/mypstot.awk                                                             

BEGIN {
# block d'initialisation
  FS=" "
  totvsz = 0
  totrss = 0
}
{
# block d'execution
  if ( $0 !~ /^USER/ ) {
    totvsz += $5 
    totrss += $6
  }
}
END {
# block final
  printf("Total VSZ = %6d\t\ttotal RSS = %6d\n", totvsz, totrss)
}

UTILISATION :

$ ps aux | awk -f ~/bin/mypstot.awk
Total VSZ = 876712              total RSS = 701040

Expressions régulières, conditionnelles, structure condionnelle et blocs exécutables

awk permet l'exécution conditionelle d'un bloc. Il suffit pour cela de préfixer le bloc à  exécuter par une expression à  valeur booléenne, i.e. la chaîne vide et l'entier 0 sont interprétés comme la valeur booléenne faux , le reste est vrai .

Les expressions régulières (ou rationnelles). 

EXEMPLES :

/^root/ { print }      # n'imprime que les lignes commençant par le motif root  

/csh$/ { print }       # n'imprime que les lignes finissant par le motif csh

/[0-9]+:[A-Za-z]*/ { print }       
                       # n'imprime que les lignes contenant un motif structuré 
                       # ainsi entier puis ":" puis chaîne de caractères

! /root/ { print }     # n'imprime que les lignes ne contenant pas le motif root

Les expressions conditionnelles. 

EXEMPLES :

$1 == "pascal" { print } # (1) imprime la ligne si le premier champ est égale à  
                         # "pascal" (égalité de chaîne de caractères)

$5 !~ /root/ { print }   # (2) imprime la ligne si le 5e champ ne contient pas l
                         # motif root 

( $1 == "foo" ) && ( $3 > 666 ) { print }       
                         # (3) imprime la ligne corresp. si le 1er champ est une 
                         # chaîne égale à  "foo" et le 3e champ un entier plus
                         # grand strictement à  666

La structure conditionnelles. awk offre l'instruction if. Tous les exemples pécédents peuvent ainsi être réécrit.

EXEMPLES :

{                           # reprise du (1) précédent                          
  if ( $1 == "pascal" ) {   
     print
  }
}

{                           # reprise du (2)
  if ( $5 !~ /root/ ) {     
     print
  }
}

{                           # reprise du (3)
  if ( ( $1 == "foo" ) && ( $3 > 666 ) ) {
     print
  }
}

Boucles

boucle while

EXEMPLE :

#
# ~/bin/test.awk
#
BEGIN {                                                                         
  FS=":"
  ORS="\t"
  nbfield=0
}
{
  x = 1
  while ( x < NF ) {  # NF var. spéciale donnant le nombre de champs (Number of 
    print $x          # Fields) de la ligne courante
    x++
  }
  print NF, " champs lus \n" 
  nbfield += NF
} 
END {
  print "\ttotal : ", nbfield, " champs lus \n" 
}


UTILISATION :

$ awk -f ~/bin/test.awk /etc/group
# $FreeBSD       src/etc/group,v 1.19.2.3 2002/06/30 17 57      4  champs lus 
        1  champs lus 
        wheel   *       0       4  champs lus 
        daemon  *       1       4  champs lus 
        kmem    *       2       4  champs lus 
        sys     *       3       4  champs lus 
        tty     *       4       4  champs lus 
        operator        *       5       4  champs lus 
        mail    *       6       4  champs lus 
        bin     *       7       4  champs lus 
        news    *       8       4  champs lus 
        man     *       9       4  champs lus 
        staff   *       20      4  champs lus 
        sshd    *       22      4  champs lus 
        smmsp   *       25      4  champs lus 
...
         total :  137  champs lus 

boucle do ... whileIci, l'évaluation de la condition se fait après la fin de la boucle.

EXEMPLE :

#
# ~/bin/test2.awk                                                               
#
BEGIN {                                                                         
  FS=":"
  ORS="\t"
  nbfield=0
}
{
  x = 1
  do {
    print $x          
    x++
  } while ( x < NF )
  print NF, " champs lus \n" 
  nbfield += NF
} 
END {
  print "\ttotal : ", nbfield, " champs lus \n" 
}


UTILISATION :

$ awk -f ~/bin/test2.awk /etc/group
# $FreeBSD       src/etc/group,v 1.19.2.3 2002/06/30 17 57      4  champs lus 
        #       1  champs lus 
        wheel   *       0       4  champs lus 
        daemon  *       1       4  champs lus 
        kmem    *       2       4  champs lus 
        sys     *       3       4  champs lus 
        tty     *       4       4  champs lus 
        operator        *       5       4  champs lus 
        mail    *       6       4  champs lus 
        bin     *       7       4  champs lus 
        news    *       8       4  champs lus 
        man     *       9       4  champs lus 
...
                total :  117  champs lus 

boucle for

EXEMPLE :

#
# ~/bin/test3.awk                                                               
#
BEGIN {                                                                         
  FS=":"
  ORS="\t"
  nbfield=0
}
{
  for ( x = 1; x < NF; x++ ) {  
    print $x          
  } 
  print NF, " champs lus \n" 
  nbfield += NF
} 
END {
  print "\ttotal : ", nbfield, " champs lus \n" 
}


UTILISATION :

$ awk -f ~/bin/test3.awk /etc/group
# $FreeBSD       src/etc/group,v 1.19.2.3 2002/06/30 17 57      4  champs lus 
        #       1  champs lus 
        wheel   *       0       4  champs lus 
        daemon  *       1       4  champs lus 
        kmem    *       2       4  champs lus 
        sys     *       3       4  champs lus 
        tty     *       4       4  champs lus 
        operator        *       5       4  champs lus 
        mail    *       6       4  champs lus 
        bin     *       7       4  champs lus 
        news    *       8       4  champs lus 
        man     *       9       4  champs lus  
...
                total :  117  champs lus 

instructions break et continueCes deux intructions permmettent de rompre le déroulement classique d'une boucle. La première permet de sortir (inconditionnellement quand elle est rencontrée) du corps de la boucle la plus interne. La seconde, quant à  elle, relance une nouvelle itération en shuntant littérallement le reste du code de la boucle.

EXEMPLE :

#                                                                               
# ~/bin/test4.awk 
#
{
  i = 1
  while (1) { # boucle infinie
     # 'continue' conduit ici
     if ( ( i % 4 ) == 0 ) {
        i++
        continue
     }
     print "iteration " i
     if ( i > 20 ) {
        break
     }
     i++
  }
  # 'break' conduit là 
  print "termine : " i
}


UTILISATION :

$ awk -f ~/bin/test4.awk 

<ENTREE>
iteration 1
iteration 2
iteration 3
iteration 5
iteration 6
iteration 7
iteration 9
iteration 10
iteration 11
iteration 13
iteration 14
iteration 15
iteration 17
iteration 18
iteration 19
iteration 21
termine : 21
^C

Type de variables

Il existe deux types scalaires : les entiers et les chaines de caractères (vus dans les exemples précédents) et un type structuré : tableau.

Attention, contrairement au langage C une chaîne de caractères n'est pas un tableau de caractères.

Opérateurs

Il s'agit des opérateurs du langages C.

Fonctions

AWK met à  la disposition du programmeur des fonctions standard prédéfinies, dont voici quelques unes :

  • length(chaine) : donne la longueur de son argument, par défaut $0

  • substr(chaine_1,int_1,int_2) : donne la sous chaine de chaine_1 commençant à  la position int_1 et de longueur inférieure ou égale à  int_2

  • split(chaine, tab, car): éclate la chaine, les sous chaine sont récupérées en tab[1], tab[2]... Le caractère car sert à  délimmiter les différents morceaux.

  • index(chaine_1, chaine_2): donne la position de la premiere occurence de chaine_2 dans chaine_1.

Variables spéciales

Il existe des variables prédéfinies dans awk, les voici :

  • FS : séparateur de champ dans le fichier (par défaut espace ou tabulation), un nombre quelconque de séparateur étant équivalent à  un seul. L'option -F permet de modifier à  l'appel de la commande la valeur de ce séparateur.

  • RS : séparateur d'enregistrement en entrée dans le fichier (par défaut le caractère de fin de ligne, un enregistrement étant alors une ligne)

  • OFS : séparateur de champ en sortie (par défaut espace)

  • ORS : séparateur d'enregistrement en sortie (par défaut le caractère fin de ligne)

  • NF : nombre de champ de l'enregistrement courant

  • NR : numéro de l'enregistrement en cours de traitement

  • FILENAME : nom du fichier en cours de traitement

Variables de type tableaux

Ce type structuré permet de stocker différents éléments (de type différents dans une structure naturellement indéxée par des chaînes de caractères. Tous les tableaux awk sont donc des tableaux associatifs, y compris ceux indexé par des entiers, puisqu'en awk un entier est représenté par sa chaîne.

La simple déclaration suivante, permet de définir un tableau :

EXEMPLE :

myarr[0] = "World"
myarr[1] = 72
myarr[2] = "Hello"                                                               

L'origine de l'indexation à  0 ou à  1 est laissé au libre choix du programmeur, awk ne pose aucun problème. La construction suivante permet d'itérer sur le contenu du tableau :

EXEMPLE :

for ( i in myarr ) {                                                            
   print myarr[i]
}

Toutefois, il n'y a aucune garantie sur l'ordre d'impression.

Suppression [delete] d'éléments dans un tableau :

EXEMPLE :

delete myarr[1] # suppression de l'éléments indexé par l'entier 1               

appartenance d'un élément à  un tableau [in] :

EXEMPLE :

{                                                                               
  elem=72
  if ( elem in myarr ) {
     ...
  }
  else {
     ...
  }
  ...
}

4.3. Exercices

Exercice 1

On dispose de différents fichiers à  la structure identique : une première ligne non pertinente qui pourra être ignorée, suivi d'un ensemble de lignes structurées en 13 champs séparés soit par des caractères espaces, soit par des caractères de tabulation. Voici un exemple de ligne :

str1 int2 int3 int4 int5 int6 int7 int8 int9 int10 int11 int12 int13

Travail à  faire. On souhaiterait faire la somme des colonnes de chacun des fichiers et écrire les résultats obtenus dans un fichier résultat, au format suivant : file_i tot2 tot3 tot4 tot5 tot6 tot7 tot8 tot9 tot10 tot11 tot12 tot13. On pourra supposer que chaque fichier dispose d'une marque explicite de fin (ici <<EOF>>).

Réponse : 

CODE :

BEGIN {
  # Proposition de Correction - Les fichiers possèdent une marque spéciale de   
  # fin : <<EOF>>

  FS= " "     # Field Separator

  OFS = "\t"
  ORS = ""
  RT = "^D"
 
  MAX=12  
  for ( i=0; i < MAX; i++ ) {
    mysum[i] = 0
    mytot[i] = 0
  }
}

{
  if ( $0 ~ /^<<EOF>>$/ ) {
    # Afficher les totaux des stats du fichier 
    print FILENAME, " "
    for ( i=0; i < MAX; i++ ) {
      printf("%5s", mysum[i])
    }
    print "\n";  
      
    # (re)initialiser le tableau 
    for ( i=0; i < MAX; i++ ) {
      mysum[i] = 0
    }  
  }
  else {
   # totaliser
   for ( i=0; i < MAX; i++ ) {
      mysum[i] += $(i+2)
      mytot[i] += $(i+2)
   }
  }
}

END {
  print "TOTAL", " "
  for ( i=0; i < MAX; i++ ) {
    printf("%5s", mytot[i])
  }
  print "\n";
}

UTILISATION :

$ awk -f sumstat.awk stat1 stat2 stat3
stat1        5    6    5    6    5    6    6    7    6    5    4    6
stat2        5    6    5    6    5    6    6    7    6    5    4    6
stat3       14   15    5    6    5   15    6    7    6    5   13    6
TOTAL       24   27   15   18   15   27   18   21   18   15   21   18

Exercice 2

Travail à  faire. Récupérer l'adresse IP et MAC des interfaces réseaux de votre machine en utilisant les valeurs données par la commande ifconfig

Exercice 3

Travail à  faire. Simuler (en partie) avec un script awk la commande wc (word count), qui sans option supplémentaire donne respectivement le nombre de ligne(s), de mot(s) (relativement à  un séparateur prédéfini), d'octet(s) du fichier qui lui est passé en argument.

Réponse : Simuler wc

 
CODE :

# ~/bin/wc.awk                                                                  

BEGIN {
# block d'initialisation                                    
  RS="\n"     # Record Separator   
  OFS="\t"    # Output Field Separator

  line=0      # var. utilisateur  
  word=0
  byte=0
}
{
# block d'execution - comptabiliser le nombre de ligne, mot, octet
    line++
    word += NF
    byte += length($0) + 1   # donne la longueur de la ligne au complet
}
END {
# block final
  print "", line, word, byte, FILENAME
}

UTILISATION :

$ wc /etc/passwd     # la commande originale                                    
       29      79    1589 /etc/passwd

$ awk -f ~/bin/wc.awk /etc/passwd
        29      79      1589    /etc/passwd

$ wc /etc/motd       # la commande originale
      24     154    1074 /etc/motd

$ awk -f ~/bin/wc.awk /etc/motd
       24      154     1074    /etc/motd

Exercice 4

Travail à  faire. Reprenons le fichier my_ps obtenu dans le chapitre précédent.

  • Affichez le total de l'occupation de la mémoire de vos processus (somme des VSZ)

  • Donnez le PID du programme le plus gourmand en mémoire (%MEM)

  • Remplacez les heures "anglaises" en leur équivalent "français"

Skins :
Transparence
Simple
Page Accueil
Formation