Table des matières
Résumé
Dans ce chapitre nous aborderons:
Qu'est-ce que gawk?
L'emploi de commandes gawk sur la ligne de commande
Comment formater du texte avec gawk
Comment gawk utilise les expressions régulières
Gawk dans les scripts
Gawk et les variables
Comme pour sed, de nombreux livres ont été écrits sur les nombreuses versions de awk. Cette introduction est loin d'être complète et vise seulement à faire comprendre les exemples des chapitres suivants.. Pour approfondir, le mieux est de débuter avec la documentation qui accompagne GNU awk: « GAWK: Effective AWK Programming: A User's Guide for GNU Awk ».
Gawk est la version GNU du programme couramment disponible sous UNIX awk, un autre éditeur par lot populaire. Du fait que le programme awk est souvent qu'un lien vers gawk, nous y référerons en tant que awk.
La fonction de base de awk est de chercher dans des fichiers des lignes ou portion de texte contenant un ou des patrons. Quand on trouve dans la ligne un des patrons, des actions sont effectuées sur cette ligne.
Un programme en awk est différent de la plupart des programmes en d'autres langages parce que un programme awk est « construit sur les données »: vous décrivez les données que vous voulez traiter puis le traitement à effectuer si elles sont trouvées. La plupart des langages sont « procéduraux. » Vous devez décrire en détail chacune des étapes du processus. En travaillant avec des langages procéduraux, il est généralement plus difficile de clairement décrire les données que le programme traitera. Pour cette raison, un programme awk est souvent agréablement facile à lire et à écrire.
Quand vous lancez awk, vous spécifiez un programme awk qui indique à awk quoi faire. Le programme consiste en une série de règles. (Il peut aussi contenir des définitions de fonctions, de boucles, des conditions et autres possibilités que nous ignorerons pour l'instant.) Chaque règle spécifie un patron à cibler et une action à effectuer sur les cibles trouvées.
Il y a plusieurs façons de lancer awk. Si le programme est court, il est plus facile de le lancer depuis la ligne de commande:
awk PROGRAM inputfile(s)
Si de multiples changements doivent être fait, peut-être régulièrement sur de multiples fichiers, il est plus facile de mémoriser les commandes awk dans un script. Ce qui se lit comme ceci:
awk -f PROGRAM-FILE inputfile(s)
La commande print de awk affiche les données sélectionnées depuis le fichier d'entrée.
Quand awk lit une ligne d'un fichier, il divise la ligne en champs basé sur le séparateur de champs en entrée, FS, qui est une variable awk (voir Section 3.2, « Les séparateurs de résultat »). Cette variable est prédéfinie avec un ou plusieurs espaces et tabulations.
Les variables $1, $2, $3, ..., $N stockent les valeurs du premier, second, troisième jusqu'au dernier champs de la ligne traitée. La variable $0 (zéro) stocke la valeur de la ligne entière. Ceci est illustré dans l'image ci-dessous, où nous voyons 6 colonnes dans l'affichage de la commande df:
Dans le résultat de ls -l, il y a 9 colonnes. L'instruction print utilise ces champs comme ceci:
kelly@octarine ~/test>ls-l| awk'{ print $5 $9 }'160orig 121script.sed 120temp_file 126test 120twolines 441txt2html.shkelly@octarine ~/test>
Cette commande a affiché la 5ème colonne d'une longue liste de fichiers, contenant la taille du fichier, et la dernière colonne, contenant le nom du fichier. Ce résultat est peu lisible à moins d'utiliser le moyen ad hoc qui est de séparer les colonnes que vous voulez voir afficher par une virgule. Dans ce cas, le caractère séparateur d'affichage par défaut, souvent un espace, sera inséré entre chaque champs de résultat.
Sans formater, avec seulement le séparateur de résultat, l'affichage est peu lisible. En insérant quelques tabulations et une chaîne pour indiquer la nature du champs ce sera bien mieux:
kelly@octarine ~/test>ls-ldh*| grep-vtotal| \ awk'{ print "Size is " $5 " bytes for " $9 }'Size is 160 bytes for orig Size is 121 bytes for script.sed Size is 120 bytes for temp_file Size is 126 bytes for test Size is 120 bytes for twolines Size is 441 bytes for txt2html.shkelly@octarine ~/test>
Notez l'effet du slash inversé, qui permet de continuer une entrée trop longue sur la ligne suivante sans que le Shell interprète cela comme des commandes distinctes. Alors qu'une ligne de commande peut être en théorie de taille illimitée, celle du moniteur ne l'est pas, et encore moins celle du papier. L'usage du slash inversé permet aussi le copier/coller de la ligne dans une fenêtre de terminal.
L'option -h de ls permet d'obtenir un format lisible de la taille des gros fichiers. L'affichage d'une longue liste avec la somme des blocs du répertoire indiquée est produite quand un répertoire est le paramètre. Cette ligne est inutile pour nous, donc nous avons mis un astérisque. Nous avons aussi ajouté l'option -d pour la même raison, dans le cas où l'expansion de l'astérisque donne un répertoire.
Le slash inversé dans cet exemple marque la continuation de la ligne. Voir Section 3.2, « Le caractère Echap (escape) ».
On peut prendre en compte autant de colonnes que l'on veut et même bouleverser l'ordre. Dans l'exemple ci-dessous on en trouve la démonstration qui affiche les partitions les plus critiques:
kelly@octarine ~>df-h| sort-rnk5| head-3| \ awk'{ print "Partition " $6 "\t: " $5 " full!" }'Partition /var : 86% full! Partition /usr : 85% full! Partition /home : 70% full!kelly@octarine ~>
Le tableau ci-dessous donne un aperçu des caractères spéciaux de formatage:
L'apostrophe, le signe Dollar et autres métacaractères devraient être protégés avec un slash inversé .
Une expression régulière peut être utilisée comme patron en l'enfermant entre slashs. L'expression régulière est alors comparée à chaque enregistrement de texte. La syntaxe est celle-ci:
awk 'EXPRESSION { PROGRAM }' file(s)
L'exemple suivant affiche seulement les informations des disques locaux, les systèmes de fichiers réseaux n'y sont pas:
kelly is in ~>df-h| awk'/dev\/hd/ { print $6 "\t: " $5 }'/ : 46% /boot : 10% /opt : 84% /usr : 97% /var : 73% /.vol1 : 8%kelly is in ~>
Le Slash doit être protégé, parce qu'il a un sens spécial pour le programme awk.
Ci-dessous un autre exemple où nous cherchons dans le répertoire /etc les fichiers qui se terminent par « .conf » et qui commencent par « a » ou « x », en employant des expressions régulières étendues:
kelly is in /etc>ls-l| awk'/\<(a|x).*\.conf$/ { print $9 }'amd.conf antivir.conf xcdroast.conf xinetd.confkelly is in /etc>
Cet exemple illustre le sens spécial du point dans les expressions régulières: le premier indique que nous voulons cibler tout caractère après la première chaîne ciblée, le second est protégé parce que il fait parti d'une chaîne à cibler (la fin du nom de fichier).
Afin de faire précéder le résultat par un commentaire, employer l'instruction BEGIN:
kelly is in /etc>ls-l| \ awk'BEGIN { print "Files found:\n" } /\<[a|x].*\.conf$/ { print $9 }'Files found: amd.conf antivir.conf xcdroast.conf xinetd.confkelly is in /etc>
L'instruction END peut être ajoutée pour insérer du texte après que le flot en entrée ait été entièrement traité:
kelly is in /etc>ls-l| \ awk'/\<[a|x].*\.conf$/ { print $9 } END { print \ "Can I do anything else for you, mistress?" }'amd.conf antivir.conf xcdroast.conf xinetd.conf Can I do anything else for you, mistress?kelly is in /etc>
Au fur et à mesure que les commandes deviennent complexes, vous voudrez les mémoriser dans un script, pour être réemployées. Un script awk contient des instructions awk définissant des patrons et des actions.
Pour illustrer nous allons produire un rapport qui affiche nos partitions les plus pleines. Voir Section 2.2, « Formater les champs ».
kelly is in ~>catdiskrep.awkBEGIN { print "*** WARNING WARNING WARNING ***" } /\<[8|9][0-9]%/ { print "Partition " $6 "\t: " $5 " full!" } END { print "*** Give money for new disks URGENTLY! ***" } kelly is in ~> df -h | awk -f diskrep.awk *** WARNING WARNING WARNING *** Partition /usr : 97% full! *** Give money for new disks URGENTLY! ***kelly is in ~>
awk d'abord affiche le message de début, puis formate toutes les lignes qui contiennent un 8 ou un 9 au début de chaque mot, suivi par un autre chiffre et le signe de pourcentage. Un message final est ajouté.
Awk est un langage de programmation . Sa syntaxe est reconnue par la plupart des éditeurs qui font la mise en relief de la syntaxe comme pour d'autres langages tel que C, Bash, HTML, etc.
Tandis que awk traite le fichier en entrée, il utilise plusieurs variables. Certaines sont modifiables, d'autres sont en lecture.
Le séparateur de champs, qui est soit un simple caractère, soit une expression régulière, contrôle la façon dont awk découpe l'enregistrement entré en champs. L'enregistrement en entrée est examiné à la recherche de séquences de caractères qui correspondent au séparateur défini; les champs eux-mêmes sont les textes entre chaque séparateur.
Le séparateur de champs est défini par la variable intégrée FS. Notez que cette variable et IFS utilisée par les Shell compatible POSIX sont distinctes.
La valeur de la variable de séparateur de champs peut être changée dans le programme awk avec l'opérateur d'assignement =. Souvent le bon moment pour faire ce changement c'est au début de l'exécution avant qu'aucune entrée n'ait été traitée, de sorte que le tout premier enregistrement est lu avec le séparateur idoine. Pour ce faire employer le patron spécial BEGIN.
Dans l'exemple ci-dessous, nous écrivons une commande qui affiche tous les utilisateurs de votre système avec une description:
kelly is in ~>awk'BEGIN { FS=":" } { print $1 "\t" $5 }'/etc/passwd--output omitted-- kelly Kelly Smith franky Franky B. eddy Eddy White willy William Black cathy Catherine the Great sandy Sandy Li Wongkelly is in ~>
Dans un script awk, cela ressemblerait à ça:
kelly is in ~>catprintnames.awkBEGIN { FS=":" } { print $1 "\t" $5 }kelly is in ~>awk-fprintnames.awk /etc/passwd--output omitted--
Choisir un séparateur de champs d'entrée soigneusement pour éviter des soucis. Un exemple pour illustrer ceci: disons que vous obtenez l'entrée sous la forme de lignes qui ressemble à ça:
« Sandy L. Wong, 64 Zoo St., Antwerp, 2000X »
Vous écrivez une commande ou un script, qui affiche le nom de la personne dans cet enregistrement:
awk 'BEGIN { FS="," } { print $1, $2, $3 }' inputfile
Mais une personne pourrait avoir un doctorat, et ça pourrait s'écrire comme ça:
« Sandy L. Wong, Doctorat, 64 Zoo St., Antwerp, 2000X »
Votre awk donnera un mauvais résultat sur cette ligne. Au besoin faire un awk supplémentaire ou un sed pour uniformiser le format des données en entrée.
Le séparateur de champs en entrée est par défaut un ou des espaces ou des tabulations.
Les champs sont habituellement séparés par des espaces dans le résultat. Ceci est visible quand vous employez la syntaxe correcte pour la commande print où les paramètres sont séparés par des virgules:
kelly@octarine ~/test>cattestrecord1 data1 record2 data2kelly@octarine ~/test>awk'{ print $1 $2}'testrecord1data1 record2data2kelly@octarine ~/test>awk'{ print $1, $2}'testrecord1 data1 record2 data2kelly@octarine ~/test>
Si vous ne mettez pas de virgule, print considérera les éléments de résultat comme un seul argument, c'est à dire il omet l'emploi du séparateur de résultat par défaut, OFS.
N'importe quel caractère peut être employé comme séparateur de champs de résultat en définissant cette variable intégrée.
Le résultat d'une instruction print est appelée un enregistrement de résultat. Chaque commande print produit un enregistrement de résultat, et ajoute une chaîne appelée le séparateur d'enregistrement de résultat, ORS(NdT: output record separator). La valeur par défaut de cette variable est « \n », le caractère saut de ligne. Donc, chaque instruction print génère une ligne distincte.
Pour modifier la façon dont les champs et les enregistrements de résultat sont séparés, assignez une autre valeur à OFS et ORS:
kelly@octarine ~/test>awk'BEGIN { OFS=";" ; ORS="\n-->\n" } \ { print $1,$2}'testrecord1;data1 --> record2;data2 -->kelly@octarine ~/test>
Si la valeur de ORS ne contient pas de saut de ligne, le résultat global tiendra sur une seule ligne.
L'intégré NR stocke le nombre d'enregistrements qui sont traités. Il est incrémenté après la lecture d'une nouvelle ligne d'entrée. Vous pouvez l'utiliser à la fin pour compter le nombre total d'enregistrements, ou à chaque enregistrement de résultat:
kelly@octarine ~/test>catprocessed.awkBEGIN { OFS="-" ; ORS="\n--> done\n" } { print "Record number " NR ":\t" $1,$2 } END { print "Number of records processed: " NR }kelly@octarine ~/test>awk-fprocessed.awk testRecord number 1: record1-data1 --> done Record number 2: record2-data2 --> done Number of records processed: 2 --> donekelly@octarine ~/test>
En plus des variables intégrées, vous pouvez définir les vôtres. Quand awk rencontre une référence à une variable qui n'existe pas (qui n'est pas prédéfinie), la variable est créée et initialisée à une chaîne nulle. Pour toutes les références suivantes, la valeur de la variable est la dernière valeur affectée. Une variable peut être une chaîne ou une valeur numérique. Le contenu des champs en entrée peut aussi être affecté à une variable.
Une valeur peut être assignée directement par l'opérateur =, ou vous pouvez utiliser la valeur courante de la variable combinée avec d'autres opérateurs:
kelly@octarine ~>catrevenues20021009 20021013 consultancy BigComp 2500 20021015 20021020 training EduComp 2000 20021112 20021123 appdev SmartComp 10000 20021204 20021215 training EduComp 5000kelly@octarine ~>cattotal.awk{ total=total + $5 } { print "Send bill for " $5 " dollar to " $4 } END { print "---------------------------------\nTotal revenue: " total }kelly@octarine ~>awk-ftotal.awk testSend bill for 2500 dollar to BigComp Send bill for 2000 dollar to EduComp Send bill for 10000 dollar to SmartComp Send bill for 5000 dollar to EduComp --------------------------------- Total revenue: 19500kelly@octarine ~>
Les raccourcis de type C comme VAR+= value sont aussi acceptés.
L'exemple de la Section 3.2, « Ecrire des fichiers de résultat » devient bien plus facile quand on utilise un script awk:
kelly@octarine ~/html>catmake-html-from-text.awkBEGIN { print "<html>\n<head><title>Awk-generated HTML</title></head>\n<body bgcolor=\"#ffffff\">\n<pre>" } { print $0 } END { print "</pre>\n</body>\n</html>" }
Et les commandes à exécuter sont aussi bien plus directe quand on utilise awk plutôt que sed:
kelly@octarine ~/html>awk-fmake-html-from-text.awk testfile>file.html
Nous nous référons encore au répertoire qui contient les scripts d'initialisation de votre système. Entrez une commande similaire à la suivante pour voir d'autres exemples pratiques de l'usage très répandu de la commande awk:
grep awk /etc/init.d/*
Pour un contrôle du format du résultat plus précis que celui fournit par print, employez printf. La commande printf peut spécifier une largeur de champs pour chaque élément, de même que divers choix de formatage des nombres (tel que la base de calcul à considérer, si il faut afficher un exposant, si le signe est à afficher, et combien de chiffres à afficher après la virgule) . Ceci est fait en ajoutant une chaîne appelée chaîne de formatage, qui contrôle comment et où afficher les autres arguments.
La syntaxe est la même que celle de l'instruction du langage C printf ; voir votre guide d'instruction C. Les pages info de gawk contiennent plein d'explications.
L'utilitaire gawk interprète un langage de programmation à but spécial, prenant en charge les travaux de reformatage de données avec seulement quelques lignes de code. C'est une version libre de la commande UNIX awk.
Cet outil lit les lignes de données entrées et peut aisément repérer le colonnage du résultat. Le programme print est le plus courant pour filtrer et formater des champs définis.
La déclaration de variable au coup par coup est directe et permet des sommations simples, des statistiques et autres opérations sur le flot en entrée traité. Les variables et les commandes peuvent être insérées dans un script awk pour un traitement en tâche de fond.
Il y a quelques exemples concret où awk peut être pratique.
Pour le premier exercice, l'entrée sont les lignes de la forme suivante:
Username:Firstname:Lastname:Telephone number
Faire un script awk qui convertit une telle ligne en un enregistrement LDAP à ce format:
dn: uid=Username, dc=example, dc=com cn: Firstname Lastname sn: Lastname telephoneNumber: Telephone number
Créer un fichier contenant quelques enregistrements de test et vérifiez..
Créer un script Bash utilisant awk et les commandes standard UNIX qui affiche les 3 utilisateurs les plus gros consommateurs d'espaces disques dans le répertoire /home (si il ne situe pas dans une partition distincte, faire le script pour la partition /; celle-ci existe sur tout système UNIX). D'abord, exécutez les commandes depuis la ligne de commande. Puis mettez-les dans un script. Le script devrait produire un résultat compréhensible (lisible par le chef). Si tout semble fonctionner, faire en sorte que le script vous envoie le résultat par mail (employez par exemple mail -s Disk space usage <you@your_comp> < result).
Si le démon des quotas tourne, utilisez ses informations; si non utilisez find.
Créer un résultat de style XML à partir d'une liste séparée par des tabulations de la forme suivante:
Meaning very long line with a lot of description meaning another long line othermeaning more longline testmeaning looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong line, but i mean really looooooooooooooooooooooooooooooooooooooooooooooooooong.
Le résultat devrait être:
<row> <entry>Meaning</entry> <entry> very long line </entry> </row> <row> <entry>meaning</entry> <entry> long line </entry> </row> <row> <entryothermeaning</entry> <entry> more longline </entry> </row> <row> <entrytestmeaning</entry> <entry> looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong line, but i mean really looooooooooooooooooooooooooooooooooooooooooooooooooong. </entry> </row>
En plus, si vous connaissez quelque peu XML, écrivez un script BEGIN et END pour compléter la table. Ou faites le en HTML.