Introduction à awk
Awk est une commande qui agit sur les différentes lignes d'un fichier textes, les unes après les autres, chaque ligne étant considérée comme un enregistrement formé de différents champs séparés par un espace (par défaut).
La syntaxe est la suivante:
awk 'condition {action}[;condition {action}]' fichier
L'ensemble condition {action} constitue une instruction.
L'action est exécutée si le condition est vérifiée. L'un ou l'autre peuvent être absents. L'action par défaut est print.
La totalité de la ligne en cours de traitement se trouve dans la variable $0, et les différents champs dans des variables $i (i=1, 2, ......NF). NF (Number of Field) est une variable interne qui contient le nombre de champ de la ligne lue.
Nous allons maintenant sur quelques exemples montrer quelques manipulations simples qui peuvent être effectuées avec awk.
Afficher le contenu d'un fichier
$ awk '1' toto
Je m'appelle Toto.
Je vais à l'école avec Tata
Nous allons à pied car ce n'est pas loin.
Elle m'aime et ça c'est cool!
Et oui Tata aime Toto et Toto aime Tata.
La vie est belle car Tata habite près de Toto.
Ils se voient tous les jours!
La condition 1 est toujours vérifiée et toutes les lignes du fichier sont donc imprimées (action par défaut). Nous avons émulé la commande cat.
Imprimer la ou les lignes contenant une expression régulière
$ awk '/vais/' toto
Je vais à l'école avec Tata
La condition est cette fois la présence d'une expression régulière dans l'input, expression régulière qui ici est une expression régulière constante: le mot « vais ». Nous avons émulé grep.
Imprimer la ou les lignes dont un champ contient une expression régulière
$ awk '$6 ~ /ta/' toto
Je vais à l'école avec Tata
La vie est belle car Tata habite près de Toto.
On a sélectionné les lignes dont le champ 6 (le sixième mot) contient « ta ».
Imprimer des lignes sur base d'une condition de départ et d'une condition d'arrêt
$ awk '/vais/,/aime/' toto
Je vais à l'école avec Tata
Nous allons à pied car ce n'est pas loin.
Elle m'aime et ça c'est cool!
Quand une ligne contenant « vais » est rencontrée, elle est imprimée et toutes les lignes suivantes jusqu'au moment où une ligne contenant « aime » est rencontrée. Cette ligne est encore imprimée mais plus les suivantes
Imprimer les lignes sur base d'une condition de départ et d'une condition d'arrêt, mais en excluant les lignes vérifiant ces conditions
awk '/aime/ {p=0} ; p ; /vais/ {p=1}' toto
Nous allons à pied car ce n'est pas loin.
Il y a cette fois 3 instructions séparées par des points-virgules et qui sont exécutées de gauche à droite pour chacune des lignes du fichier. L'action de la 3ième instruction consiste à donner à p la valeur 1. Cette action est exécutée à la lecture de la 2ième ligne du fichier. Les deux autres instructions ont été jusqu'alors inopérantes. Lors de la lecture de la 3ième ligne, p vaut 1, la condition p de la 2ième instruction est vérifiée, ce qui déclenche l'action par défaut print de cette instruction. A la lecture de la ligne suivante, p est remis à zéro et la 2ième instruction, celle qui imprime, devient inopérante.
Imprimer la première ligne vérifiant une condition et toutes les suivantes jusqu'à la fin du fichier
$ awk '/oui/ {p=1} ; p' toto
Et oui Tata aime Toto et Toto aime Tata.
La vie est belle car Tata habite près de Toto.
Ils se voient tous les jours!
On a deux instructions séparées par un point-virgule. Lorsque « oui » est rencontré, p se voit assigné la valeur 1, ce qui rend vraie la condition p de l'instruction suivante et provoque l'impression de la ligne avec « oui » et de toutes les suivantes puisque p n'est plus changé.
Ou:
$ awk '/oui/,0' toto
Et oui Tata aime Toto et Toto aime Tata.
La vie est belle car Tata habite près de Toto.
Ils se voient tous les jours!
La condition 0 est toujours fausse!
Imprimer une partie d'un fichier sur base des numéros de lignes
$ awk 'NR>=5' toto
Et oui Tata aime Toto et Toto aime Tata.
La vie est belle car Tata habite près de Toto.
Ils se voient tous les jours!
NR (Number of Record) est une variable qui indique le nombre d'enregistrements lus, c'est-à-dire le numéro de ligne. Nous avons imprimé les lignes dont le numéro est plus grand ou égal à 5.
$ awk 'NR==3,NR==5 {print NR-2,$0}' toto
1 Nous allons à pied car ce n'est pas loin.
2 Elle m'aime et ça c'est cool!
3 Et oui Tata aime Toto et Toto aime Tata.
Nous avons cette fois sélectionné les lignes dont le numéro est compris entre 3 et 5 (limites incluses) et nous avons remplacé l'action par défaut par l'impression du numéro de ligne diminué de 2 suivi de la ligne lue (dont le contenu est dans la variable $0)
Imprimer sous forme de tableau certains champs (mots) d'un fichier
awk 'BEGIN {OFS="|";print "Champ 3\t","champ 2";print "=======\t","======="} ; {print $3"\t",$2}' toto
Champ 3 |champ 2
======= |=======
Toto... |m'appelle
à...... |vais
à...... |allons
et..... |m'aime
Tata... |oui
est.... |vie
voient. |se
La première instruction comprend la condition spéciale BEGIN qui est VRAIE tant que la lecture du fichier n'a pas débuté. Dans cette instruction figurent plusieurs actions dont la première consiste à donner à la variable interne OFS (Output Field Separator) une valeur différente celle par défaut (normalement un espace). Ensuite nous imprimons les entêtes. Il est fait usage du caractère de contrôle tabulation représenté par \t. L'instruction suivante imprime les champs choisis.
Compter les mots dans un fichier
$ awk ' {n = n + NF} ; END {print n}' toto
49
L'action de la 2ième instruction est liée à la condition spéciale END qui est portée à VRAIE lorsque la totalité du fichier a été lue.
Chercher et remplacer
$ awk '{gsub(/Tata/,"Titine"); print}' toto
Je m'appelle Toto.
Je vais à l'école avec Titine
Nous allons à pied car ce n'est pas loin.
Elle m'aime et ça c'est cool!
Et oui Titine aime Toto et Toto aime Titine.
La vie est belle car Titine habite près de Toto.
Ils se voient tous les jours!
La commande awk est suivie d'une seule instruction qui comprend deux actions successives: remplacer « Tata » par «Titine » et ensuite imprimer la ligne.
Et si on ne met pas le print?
$ awk '{gsub(/Tata/,"Titine")}' toto
$
Il n'y a pas d'output: l'instruction awk comprend déjà une action et il n'y a donc pas lieu de considérer une action par défaut.
Supprimons les accolades:
$ awk 'gsub(/Tata/,"Titine")' toto
Je vais à l'école avec Titine
Et oui Titine aime Toto et Toto aime Titine.
La vie est belle car Titine habite près de Toto.
gsub(/Tata/,"Titine") n'est plus considéré comme une action mais comme une condition qui est VRAIE lorsqu'il y a effectivement une substitution (dans ce cas gsub retourne 1), ce qui déclenche l'action par défaut (impression). Donc seules sont imprimées les lignes du fichier qui ont été modifiées.
Si on utilise sub au lieu de gsub, on remplace seulement la première occurrence de Tata:
$ awk 'sub(/Tata/,"Titine")' toto
Je vais à l'école avec Titine
Et oui Titine aime Toto et Toto aime Tata.
La vie est belle car Titine habite près de Toto.
(Le 2ième Tata a survécu).
Pour remplacer une occurrence particulière, on doit utiliser gensub. Contrairement à sub ou gsub, gensub ne renvoie pas 1 ou 0, mais la ligne, modifiée ou pas, reçue en input, l'original restant inchangé. Il convient donc d'assigner à $0 le retour de gensub:
$ awk '{x=$0 ; $0=gensub(/Tata/,"Titine",2)}; !(x==$0) {print}' toto
Et oui Tata aime Toto et Toto aime Titine.
Nous sauvegardons la valeur initiale de $0 dans x, et nous imprimons seulement si $0 n'a pas changé: condition !(x==$0) de la 2ième instruction.
Conversion des fins de ligne Unix au format DOS
Dans un système Unix, les lignes se terminent par le caractère de contrôle nouvelle ligne (0A en hexadécimal). Dans un système DOS ce caractère nouvelle ligne est précédé du caractère de contrôle retour chariot (OD en hexadécimal).
Nous disposons d'un fichier salut qui contient le seul mot Salut.
$ cat salut
Salut
Au lieu d'envoyer directement le résultat de la commande vers l'écran, faisons le transiter par la commande od afin d'avoir, par suite des options fournies à cette commande, un output en hexadécimal:
$ cat salut | od -An -tx1
53 61 6c 75 74 0a
Nous voyons apparaître en dernier le fameux caractère de contrôle.
Essayons ceci:
$ awk 'BEGIN {ORS="\r\n"};1' salut | od -An -tx1
53 61 6c 75 74 0d 0a
Et voilà! Nous avons une fin de ligne au format DOS.
Lorsque awk lis une ligne, il la débarrasse de son caractère de contrôle final. L'action print remet par défaut à la fin de la ligne un caractère de contrôle nouvelle ligne. Ce comportement peut-être modifié en jouant sur le contenu de la variable interne ORS (Output Record Separator). Sachant que \r représente le caractère de contrôle retour chariot et \n le caractère de contrôle nouvelle ligne, on voit que la valeur assignée à ORS, est bien celle qui convient.
Conversion des fin de ligne DOS au format Unix
Soit salut.dos un fichier avec fin de ligne au format DOS
$ cat salut.dos | od -An -tx1
53 61 6c 75 74 0d 0a
Convertissons le au format Unix:
awk '{sub(/\r$/,"");print}' salut.dos | od -An -tx1
53 61 6c 75 74 0a
Sachant que $ est l'indicateur de fin de ligne, on voit que la commande sub remplace le caractère \r qui se trouve à la fin de la ligne (puisque \n a été enlevé à la lecture) par rien, c'est à dire le supprime. L'action print remet ensuite un \n à la fin.
Statistiques sur la fréquence des mots
Nous disposons d'un fichier prénoms qui se présente comme suit:
12345:Arthur
62345:Firmin
12445:Arthur:Motta:OK
12345:Isidore
72345:Arthur::KO
16545:Firmin
18345:Firmin
12345:Anatole:Dubois:KO
12645:Arthur:Dupont
12345:Arthur
12845:Anatole
14582:Grocanar
Nous aimerions établir une liste des prénoms classés suivant leur fréquence.
A moi awk!
$ awk -F ":" '{a[$2]++} ; END {for (x in a) print a[x]"\t" x | "sort -nr"}' prénoms
5 Arthur
3 Firmin
2 Anatole
1 Isidore
1 Grocanar
L'option -F fixe le séparateur de champ à « : ». D'autre part, awk peut travailler avec des tableaux dont l'indice n'est pas un nombre entier mais une chaîne de caractère. Ainsi l'instruction a[$2]++ crée d'abord l'élément a[Arthur] du tableau a avec la valeur 1, ensuite l'élément a[Firmin ] avec la même valeur 1, puis la valeur de a[Arthur] est incrémentée de 1 etc... En final, on se trouve avec le tableau:
a[Arthur] = 5
a[Firmin] = 3
a[Isidore] = 1
a[Anatole] = 2
a[Grocanar] = 1
Il reste à imprimer ce tableau, ce qui est réalisé dans l'instruction conditionnée par END. for (x in a)signifie que x parcourt toutes les valeurs de l'indice de a (tous les prénoms). L'argument de print est constitué de a[x] et x séparés par un caractère de tabulation \t. Le résultat des différentes commandes print est mis en cache pour être envoyé ensuite à la commande externe sort.
Considérons maintenant de nouveau le fichier toto. Nous souhaitons établir une liste des mots contenus dans ce fichier, classés suivant leur fréquence. Pour raccourcir la liste, nous éliminons les mots trop courts et ceux qui n'apparaissent qu'une seule fois:
$ awk '{gsub(/[.;,!?]/,""); gsub(/\47/," "); $0=tolower($0) ; for (i = 1; i <= NF; i++) if (length($i) > 2) a[$i]++} ; END {for (x in a) if (a[x] > 1) print a[x]"\t" x | "sort -nr"}' toto
4 toto
4 tata
3 est
3 aime
2 car
Bien plus qu'une simple commande, awk est un véritable langage de programmation et pour peu que les instructions soient nombreuses ou complexes, une ligne de commande telle que celle ci‑dessus devient vite incompréhensible. Aussi est-il préférable de regrouper les instructions dans un fichier texte tel que celui-ci:
#!/usr/bin/awk -f
BEGIN {
print "Fréquence", "Mot"
print "=========", "==="
}
{
gsub(/[.;,!?]/,"")
# suppression de la ponctuation
gsub(/\47/," ")
# remplacement des apostrophes (47 en octal) par un espace
$0=tolower($0)
# remplacement des majuscules par des minuscules
for (i = 1; i <= NF; i++)
if (length($i) > 2) a[$i]++
}
END {
for (x in a)
if (a[x] > 1) {
print a[x]"\t " x | "sort -nr"
}
}
Nous avons ajouté une instruction conditionnée par BEGIN, ainsi que des commentaires et appelé le fichier statmots. On peut maintenant procéder comme suit:
$ awk -f statmots toto
Fréquence Mot
========= ===
4 toto
4 tata
3 est
3 aime
2 car
L'option -f dit à awk de trouver ses instructions dans le fichier qui suit. La ligne shebang n'est pas nécessaire si on procède de cette façon. Cependant grâce à cette ligne shebang et pour peu qu'on ai rendu statmots exécutable (chmod +x statmots), on peut également procéder comme ceci:
$ ./statmots toto
Fréquence Mot
========= ===
4 toto
4 tata
3 est
3 aime
2 car
Les quelques exemples donnés ici ne montrent qu'un petit aperçu des nombreuses possibilités offertes par awk.
Post a Comment