Awk en pratique

Cet article a pour but de vous présenter awk au cours de quelques simples applications pratiques. Le lecteur pourra se référer à la page man de awk pour plus d'informations et aux articles déja parus dans linux magazine france et sur lea-linux.org par exemple. Vous pourrez trouver un article présentant awk de façon plus approfondie sur mon site dont l'url est précisé en fin d'article. Outre awk, cet article sera l'occasion de vous présenter quelques facilités offertes par linux.

Pratiquons

L'objectif de ce magazine est de montrer des applications concrètes aux outils que l'on peut trouver sous linux. Je vais donc suivre cette ligne éditoriale en ne faisant pas un cours sur awk mais en commentant des simples exemples fonctionnels. Je vous présenterai également au cours de l'article quelques astuces plus générales vous permettant de personnaliser votre système. Les exemples de cet article ont été créés sur une mandrake8.2 (aka Bluebird) mais seront certainement portables sur d'autres distributions, notamment la slackware 8 et la debian 3 sur lesquelles ces manipulations ont été testées avec succès.

Numéroteur de fichier

Voyons en premier exemple la numérotation des lignes d'un fichier. Ouvrez une console dans votre environnement graphique favori ou tapez directement en mode texte :

awk '{printf "%03u : %s\n",FNR,$0}' ~/.kderc

Cette commande invoque awk, lui passe en paramètre un script placé entre cotes et un fichier sur lequel appliquer ce script, ici .kderc, dans le répertoire de l'utilisateur symbolisé sous unix par un ~ (et également par la variable d'environnement HOME, tapez echo ~ ou echo $HOME pour vous en convaincre). Examinons en détails ce script simple. Il est constitué d'un bloc placé entre accolades. Ce bloc est interprété pour chaque enregistrement du fichier passé en paramètre. Par défaut, en awk, un enregistrement correspond à une ligne.

Le bloc à exécuter contient une commande printf servant comme en beaucoup d'autres langages comme le php, ou le C à afficher une chaîne de caractères formattée. On affiche ici un entier sur minimum 3 caractères préfixé si nécessaire par des zéros (%03u) suivi du signe ":" et d'une chaîne (%s). L'entier et la chaîne sont spécifiés à la suite de la chaîne de formatage. Il s'agit pour l'entier de FNR, qui signifie en awk "Number of Records of the current input file seen so far", soit le nombre d'enregistrements traités jusqu'à maintenant y compris l'enregistrement courant dans le fichier actuel ou, dans notre cas, le numéro de la ligne en cours de traitement. La chaîne affichée est $0, c'est à dire en awk, l'enregistrement complet, soit ici la ligne de fichier.

Cette commande nous affiche donc le contenu du fichier mais où chaque ligne est préfixée par son numéro, par exemple, la commande ci-dessus provoque l'affichage suivant sur le portable qui m'a servi pour la rédaction de cet article ( l'affichage différera surement chez vous ) :

001 : [General]
002 : background=189,189,189
003 : foreground=0,0,0
004 : selectBackground=0,128,128
005 : selectForeground=255,255,255
006 : windowBackground=255,255,255
007 : windowForeground=0,0,0

Version script

On voudrait disposer d'une commande plus simple pour numéroter nos fichiers sans avoir à retaper cette ligne de commande sans fin ;-). On va donc créer un script. Appelons le numerote_fic. Utilisez votre éditeur de texte favori pour mettre dans un fichier numerote_fic les lignes suivantes :

#!/bin/awk -f
{
printf "%02u : %s\n",FNR,$0;
}

La première ligne designe par quel interpréteur doit être "lu" le script, ici awk, qui se trouve dans le répertoire/bin. On lui passe l'argument -f pour lui spécifier de lire le script dans le fichier. Les lignes suivantes constituent le corps du script. On rend ce script exécutable en tapant chmod a+x numerote_fic (changer le mode, ajouter le droit en exécution à tous pour le fichier numerote_fic).

Vous pouvez à présent taper ./numerote_fic ~/.kderc pour obtenir un résultat équivalent à celui que l'on avait précédemment. La ligne est cependant plus courte à retenir.

Version nouvelle commande

Si vous vous déplacez dans l'arborescence, vous devrez spécifier le chemin vers numerote_fic à chaque fois que vous en avez besoin. Par exemple, chez moi : /home/xavier/devel_awk/numerote_fic main.c si je veux afficher le fichier main.c du répertoire courant dans sa version numérotée.

On veux donc se créer une nouvelle commande accessible quel que soit le répertoire dans lequel nous nous trouvions. Nous avons pour ce faire deux solutions. Soit on ajoute le répertoire contenant le fichier numerote_fic à la variable d'environnement PATH soit on créée un alias. Je me permets d'ouvrir ici une parenthèse hors sujet pour vous apprendre ou vous rappeler que sous unix et donc linux le répertoire courant peut être affiché grâce à la commande pwd et qu'il est contenu dans la variable d'environnement PWD.

On admettra que vous copeiez le fichier du script numerote_fic dans un sous-répertoire bin dans votre répertoire personnel, son chemin est donc ~/bin/numerote_fic puisque, rappelons le encore, ~ représente votre répertoire personnel.
Si vous avez choisi la solution avec modification du PATH, il suffit de taper export PATH=~/bin:$PATH ce qui a pour effet d'ajouter à la variable d'environnement PATH le sous-répertoire bin de votre répertoire d'utilisateur. Ainsi, lorsque vous taperez une commande, le système cherchera si elle correspond à un fichier exécutable dans ce répertoire.
Si vous avez choisi la méthode de l'alias, vous devez taper alias numerote_fic='~/bin/numerote_fic'. Dans les deux cas, vous aurez ainsi la possibilité d'utiliser la nouvelle commande numerote_fic n'importe où dans l'arborescence de votre disque dur.

De façon définitive

Si vous trouvez cette commande numerote_fic pratique, vous ne voudrez pas retaper à chaque fois que vous redémarrez votre machine la commande permettant d'accéder à numérote_fic. Pour celà, vous pouvez modifier le fichier .bashrc, dans votre répertoire utilisateur. Vous pouvez donc y ajouter la ligne export PATH=~/bin:$PATH ou alias numerote_fic='~/bin/numerote_fic' dans ce fichier pour que les modifications soient définitives. Attention toutefois ! Les modifications apportées au fichier .bashrc ne sont effectives que lors de la prochaine ouverture de session (nouvelle console en environnement graphique, redémarrage, login en mode texte, relecture forcée du fichier par /bin/sh ~/.bashrc).

D'autres exemples

Maintenant que nous avons vu comment créer un script et le rendre accessible, nous allons voir quelques scripts utilitaires supplémentaires pouvant vous rendre la vie plus simple avec votre GNU/Linux.

Compteur de lignes

Editez un fichier compte_lignes et placez y ceci :

#!/bin/awk -f
END {
        print FNR " lignes dans ." ARGV[1]; 
}

Cet exemple affiche le nombre de lignes contenues dans un fichier passé en paramètre. On retrouve la variable FNRet on découvre ARGV, un tableau constitué de tous les paramètres passés au script. On accède aux éléments du tableau grâce à la notation à crochets : ARGV[1], ARGV[2], ARGV[3], ... ARGV[n] sont les n paramètres passés au script.

Vous vous interrogez surement sur la signification du mot clé END... Un script awk est constitué de blocs, c'est à dire de code placé entre accolades. Par défaut, chaque bloc est exécuté pour chaque ligne du fichier passé en paramètre. On peut modifier ce comportement en plaçant devant le bloc une condition, par exemple BEGIN ou END. Un bloc préfixé par BEGIN ne sera exécuté qu'une fois, au lancement du script. De même, un bloc préfixé par END ne sera exécuté qu'une fois, à la fin du script.

Si vous oubliez de passer un nom de fichier au script il compte les lignes que vous lui tapez sur l'entrée standard, ce qui est d'un intérêt limité. Nous allons donc améliorer notre script pour lui faire tester la présence ou non de paramètres. En plus nous allons permettre que plusieurs fichiers soient passés en paramètre.

#!/bin/awk -f
BEGIN {
	if (ARGC==1) exit
	encours=1
	lignes=0
}
encours<ARGIND {
	print ARGV[encours]" : " lignes " lignes."
	lignes=0
	encours=ARGIND
}
{
	lignes++
}
END {
	if (lignes) print ARGV[encours]" : " lignes " lignes."
}

Le bloc begin teste la valeur de ARGC. ARGC est égale au nombre de paramètres plus 1. S'il vaut 1, cela veut dire qu'il n'y a pas de paramètre. Ces ARGC chaînes de caractères sont stockées dans le tableau ARGV. Comme dit plus haut, on y trouve les paramètres en ARGV[1], ..., ARGV[ARGC-1] et ARGV[0] contient le nom de l'exécutable lancé, c'est à dire non pas le nom du script mais l'exécutable awk lancé qui est selon les cas awk, gawk ou mawk.
Dans notre cas, si ARGC vaut 1, on sort du script, grâce à la fonction exit. La syntaxe d'une expression conditionelle en awk est simple et semblable à celle du C :

if (condition) {
        instructions si condition vraie
} else {
        instructions si condition fausse
}

Si on poursuit l'exécution, on définit deux variables : encours qui sera pour nous l'index du fichier dont on compte les lignes et lignes, qui contiendra le nombre de lignes du fichier en cours.

Le bloc suivant n'est exécuté que si encours est inférieur à ARGIND. ARGIND est une variable prédéfinie de awk contenant l'index du fichier en cours. Ce bloc affiche le nombre de lignes du fichier que l'on vient de terminer de lire et met à jour les variables lignes et encours.

Le court bloc suivant incrémente la variable lignes.

Le dernier bloc est exécuté à la fin du script. Il affiche le dernier fichier et son nombre de lignes.

Testons notre commande dans le répertoire en cours :

$ ./compte_fic compte_fic numerote_fic 
compte_fic : 17 lignes.
numerote_fic : 5 lignes.

Remarquez qu'elle peut également s'utiliser ainsi, l'interpréteur se charge tout seul de trouver les fichiers correspondant à *_fic :

$ ./compte_fic *_fic
compte_fic : 17 lignes.
numerote_fic : 5 lignes.

Décommenteur

On va créer un script supprimant tous les commentaires et les lignes vides d'un fichier de configuration tel que smb.conf ou httpd.conf ou n'importe quel fichier dont les commentaires sont signalés par le caractère #. Ceci va nous permettre de voir un second aspect d'awk. Chaque enregistrement (ligne par défaut) est constitué de champs. Par défaut on considère que les champs sont délimités par des espaces mais on peut altérer cet état de fait en modifiant la variable prédéfinie FS (pour Field Separator). Les champs sont accessibles par les variables $1, $2, ... $n.

Dans un fichier, les commentaires commencent après le premier #, c'est à dire $2, le deuxième champ, $1 représente donc les données utiles. On les affichera donc grâce à print $1.

Toutefois, il ne nous faut pas exécuter cette instruction si la ligne est vide ou si elle n'est constituée que d'un commentaire. On va donc placer une condition avant le bloc en utilisant les expressions régulières (c'est un abus de langage, on doit théoriquement les appeler expressions rationelles). Les expressions rationelles sont placées entre /. ^ et $ représentent respectivement le début et la fin de ligne. Donc une ligne vide est représentée par /^$/ et une ligne ne contenant qu'un commentaire, c'est à dire commençant par un dièse se note /^#/. Les lignes intéressantes sont celles ne correspondant à aucune de ces expressions rationelles. Sachant que le NON logique se note ! et un OU || il devient clair que la condition se note !(/^#/ || /^$/).

Il nous reste un dernier détail à ajouter à notre raisonnement : les espaces ! Une ligne ne contenant que des espaces est une ligne vide mais ne correspond pas à ^$. Pour spécifier dans une expression rationelle "zéro ou plus caractères c" on écrit c*. On peut donc compléter notre condition et écrire notre script :

#!/bin/awk -f
BEGIN {
	FS="#"
}
!(/^ *#/ || /^ *$/) {
	print $1
}

Le bloc BEGIN modifie la valeur de FS à #. Ensuite pour chaque enregistrement contenant des caractères utiles on n'affiche que les caractères utiles. Le gain de place est énorme, comme le prouve la sortie ci-dessous. Le fichier est moins lisible pour un débutant mais un expert y trouve ainsi plus vite ses petits.

[xavier@zaz2 devel_awk]$ ./decommente_fic /etc/samba/smb.conf > ./smb.conf
[xavier@zaz2 devel_awk]$ ./compte_fic /etc/samba/smb.conf ./smb.conf 
/etc/samba/smb.conf : 411 lignes.
./smb.conf : 137 lignes.

C'en est fini pour cette introduction à awk. J'espère vous avoir donné envie de fouiller un peu plus les possibilités de ce langage qui se révèle un allié de choix pour les traitement de flux de caractères ou la réalisation de filtres de conversion. Il fait partie des outils historiques incontournables des UNIX avec ses amis sed et grep pour ne citer qu'eux.

Xavier Garreau - <xavier@xgarreau.org>
http://www.xgarreau.org/

Ingénieur de recherche PRIM'TIME TECHNOLOGY
http://www.prim-time.com/

Membre fondateur du ROCHELUG
http://www.rochelug.org/

Références :

a+

Auteur : Xavier GARREAU
Modifié le 10.09.2004