« GAWK » : différence entre les versions
m (→Divers) |
|||
(4 versions intermédiaires par un autre utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
[[ | [[Catégorie:Développement]] | ||
[[Catégorie:Commandes Unix]] | |||
= Introduction à (g)awk = | = Introduction à (g)awk = | ||
Ligne 9 : | Ligne 11 : | ||
=== Présentation === | === Présentation === | ||
Vous vous demandez surement pourquoi ce (g) avant awk, non ? Ce n'est pas le g de | Vous vous demandez surement pourquoi ce (g) avant awk, non ? Ce n'est pas le g de [[GNOME]], ni celui de gtk+ mais celui de GNU. Extrait de la page de manuel (<span class="code">man gawk</span> ou <span class="code">man awk</span>) :<br />''"Gawk est l'implémentation du projet GNU du langage de programmation AWK, lequel se conforme à la définition du langage dans la norme '''POSIX1003.2 Command Language and Utilities Standard'''. Cette version est basée, elle, sur la description d'AWK de Aho, Kernighan and Weinberger, plus les spécificités supplémentaires se trouvant dans la version de awk de l'UNIX System V version 4. Gawk ajoute également des extensions plus récentes des Laboratoires Bell ainsi que des extensions spécifiques à GNU."''<br /> Je ne mets pas ce pavé pour remplir de l'espace mais pour que les personnes à la base de choses que je ne fais que présenter soient reconnues pour leur travail et que l'on sache de quoi on parle. | ||
Nous allons pouvoir commencer: | Nous allons pouvoir commencer: | ||
Ligne 18 : | Ligne 20 : | ||
{| border="1" | {| border="1" | ||
| <code> BEGIN </ | | <span class="code"> BEGIN </span> | ||
| Le bloc qui suit est exécuté au début du programme, '''une seule fois'''. | | Le bloc qui suit est exécuté au début du programme, '''une seule fois'''. | ||
|- | |- | ||
| <code> END </ | | <span class="code"> END </span> | ||
| Le bloc qui suit est exécuté à la fin du programme, '''une seule fois'''. | | Le bloc qui suit est exécuté à la fin du programme, '''une seule fois'''. | ||
|- | |- | ||
| <code> /expression régulière/ </ | | <span class="code"> /expression régulière/ </span> | ||
| Le bloc qui suit est exécuté si les données en cours de traitement correspondent à l'expression régulière. Si vous ne connaissez pas les expressions régulières, lisez la page <code>man egrep</ | | Le bloc qui suit est exécuté si les données en cours de traitement correspondent à l'expression régulière. Si vous ne connaissez pas les expressions régulières, lisez la page <span class="code">man egrep</span>.<br />'''Un exemple''' : <span class="code">/^#/</span> Permet de selectionner les lignes commençant par un #. | ||
|- | |- | ||
| <code> expression relationnelle </ | | <span class="code"> expression relationnelle </span> | ||
| Le bloc qui suit est exécuté si l'expression est vraie.<br />'''Un exemple''' : <code>maVariable==5</ | | Le bloc qui suit est exécuté si l'expression est vraie.<br />'''Un exemple''' : <span class="code">maVariable==5</span> | ||
|- | |- | ||
| <code> modèle && modèle </ | | <span class="code"> modèle && modèle </span> | ||
| Le bloc qui suit est exécuté si les deux modèles sont vérifiés. | | Le bloc qui suit est exécuté si les deux modèles sont vérifiés. | ||
|- | |- | ||
| <code> modèle || modèle </ | | <span class="code"> modèle || modèle </span> | ||
| Le bloc qui suit est exécuté si au moins un des modèles est vérifié. | | Le bloc qui suit est exécuté si au moins un des modèles est vérifié. | ||
|- | |- | ||
| <code> modèle0 ? modèle1 : modèle2 </ | | <span class="code"> modèle0 ? modèle1 : modèle2 </span> | ||
| Le bloc qui suit est exécuté si les modèles 0 et 1 sont vérifiés ou si le modèle2 uniquement est vérifié. | | Le bloc qui suit est exécuté si les modèles 0 et 1 sont vérifiés ou si le modèle2 uniquement est vérifié. | ||
|- | |- | ||
| <code> (modèle) </ | | <span class="code"> (modèle) </span> | ||
| Permet de grouper les modèles.<br /> Un exemple : <code>(modèle0 || modèle1) && modèle2</ | | Permet de grouper les modèles.<br /> Un exemple : <span class="code">(modèle0 || modèle1) && modèle2</span> | ||
|- | |- | ||
| <code> ! modèle </ | | <span class="code"> ! modèle </span> | ||
| Le bloc qui suit est exécuté si le modèle n'est pas vérifié. | | Le bloc qui suit est exécuté si le modèle n'est pas vérifié. | ||
|- | |- | ||
| <code> modèle1, modèle2 </ | | <span class="code"> modèle1, modèle2 </span> | ||
| Le bloc qui suit est exécuté pour la partie des données en cours commençant par modèle1 et finissant par modèle2.<br />'''Un exemple''' : <code>/\/\*/, /\*\//</ | | Le bloc qui suit est exécuté pour la partie des données en cours commençant par modèle1 et finissant par modèle2.<br />'''Un exemple''' : <span class="code">/\/\*/, /\*\//</span> pour afficher les commentaires multilignes | ||
|} | |} | ||
Ligne 55 : | Ligne 57 : | ||
Pour awk, les blocs de données lues sont séparés par un caractère contenu dans la variable '''RS''' (Record Separator, retour à la ligne par défaut). On peut accéder à l'intégralité du bloc grâce à la variable '''$0'''. C'est ce qu'on appelle un enregistrement. | Pour awk, les blocs de données lues sont séparés par un caractère contenu dans la variable '''RS''' (Record Separator, retour à la ligne par défaut). On peut accéder à l'intégralité du bloc grâce à la variable '''$0'''. C'est ce qu'on appelle un enregistrement. | ||
'''Un exemple''' : Tapez <code>awk '{ print $0 }' un_fichier</ | '''Un exemple''' : Tapez <span class="code">awk '{ print $0 }' un_fichier</span><br /> Passionnant, non ? vous venez de réinventer la commande <span class="code">cat</span> ;-) ! <br />'''Un autre exemple''' : Tapez <span class="code">awk '/\/\*/, /\*\// { print $0 }' un_fichier_c_avec_des_commentaires</span><br /> C'est déjà plus sympa, hein ? <br />'''Un dernier exemple''' : Tapez <span class="code">awk '! (/^ *#/ || /^$/) { print $0 }' un_fichier_conf</span><br /> Vous n'aviez jamais vu votre fichier de config d'Apache comme ça, si ? | ||
=== Les champs === | === Les champs === | ||
Ah, la nature ... Courir nus dans les champs en se tenant la main ... '''NON''', je vous arrête tout de suite.<br /> Chaque enregistrement est séparé en champs par un caractère contenu dans la variable FS (Field Separator, un espace par défaut). Les champs sont accessibles par les variables $1, $2, $3, etc ...<br />Pour afficher le 3<sup>ème</sup> champ de toutes les lignes de vos données à traiter, vous utilisez <code>print $3</ | Ah, la nature ... Courir nus dans les champs en se tenant la main ... '''NON''', je vous arrête tout de suite.<br /> Chaque enregistrement est séparé en champs par un caractère contenu dans la variable FS (Field Separator, un espace par défaut). Les champs sont accessibles par les variables $1, $2, $3, etc ...<br />Pour afficher le 3<sup>ème</sup> champ de toutes les lignes de vos données à traiter, vous utilisez <span class="code">print $3</span>.<br />'''Un exemple''' : <span class="code">awk -F ':' '{ print $1 " est " $5 }' /etc/passwd</span> pour afficher la description des utilisateurs de votre machine. | ||
=== Les variables === | === Les variables === | ||
Ligne 76 : | Ligne 78 : | ||
|- | |- | ||
| ENVIRON | | ENVIRON | ||
| Un tableau contenant les variables d'environnement. '''Exemple''' <code>ENVIRON["PATH"]</ | | Un tableau contenant les variables d'environnement. '''Exemple''' <span class="code">ENVIRON["PATH"]</span> contient votre path. | ||
|- | |- | ||
| FIELDWIDTHS | | FIELDWIDTHS | ||
| Pour le cas de traitement de données non délimités mais contenant des champs de largeur fixe.<br /> Cette variable est de la forme <code>largeur_champ_1 largeur_champ_2 largeur_champ_3 etc...</ | | Pour le cas de traitement de données non délimités mais contenant des champs de largeur fixe.<br /> Cette variable est de la forme <span class="code">largeur_champ_1 largeur_champ_2 largeur_champ_3 etc...</span>. | ||
|- | |- | ||
| FNR | | FNR | ||
| Le numéro de l'enregistrement actuellement en cours de traitement.<br />'''Un exemple''' : <code>awk '{print FNR ": " $0}' un_fichier</ | | Le numéro de l'enregistrement actuellement en cours de traitement.<br />'''Un exemple''' : <span class="code">awk '{print FNR ": " $0}' un_fichier</span> permet d'afficher le fichier avec les numéros de lignes.<br />'''Un autre exemple''' : <span class="code">awk '{print FNR ": " $0}' un_fichier | grep ^45:</span> permet d'afficher la ligne 45 du fichier ''un_fichier''. On fait appel à grep, ce n'est plus du 100% awk mais c'est beau la diversité, non ? | ||
|- | |- | ||
| FS | | FS | ||
Ligne 103 : | Ligne 105 : | ||
; '''Exemples :''' | ; '''Exemples :''' | ||
: <code>maVar=3</ | : <span class="code">maVar=3</span> déclare et initialise la variable myVar à la valeur 3.<br /><span class="code">print maVar</span> affiche le '''contenu de la variable maVar'''.<br /><span class="code">print $maVar</span> affiche le '''champ n°maVar''' de l'enregistrement en cours (ici, '''équivaut à print $3''').<br /><span class="code">print $(maVar-1)</span> affiche le '''champ n°(maVar-1)''' de l'enregistrement en cours (ici, '''équivaut à print $2''').<br /><span class="code">split("toto:tata:titi:tutu", arr, ":")</span> initialise le tableau arr à ("toto","tata",...). On accède ensuite aux valeurs par <span class="code">arr[1], arr[2], ..., arr[4]</span> | ||
=== Les fonctions === | === Les fonctions === | ||
Ligne 109 : | Ligne 111 : | ||
Il y en a de nombreuses : <br /> | Il y en a de nombreuses : <br /> | ||
* pour l'affichage formaté, de textes et de nombres. <code>'''print printf ...'''</ | * pour l'affichage formaté, de textes et de nombres. <span class="code">'''print printf ...'''</span> | ||
* pour les opérations habituelles sur les chaines de caractères, concaténation, substitution, remplacement, recherche ... <code>'''gensub substr tolower index split ...'''</ | * pour les opérations habituelles sur les chaines de caractères, concaténation, substitution, remplacement, recherche ... <span class="code">'''gensub substr tolower index split ...'''</span> | ||
* pour les accés aux systèmes de fichiers et au système <code>'''fflush close nextline system'''</ | * pour les accés aux systèmes de fichiers et au système <span class="code">'''fflush close nextline system'''</span> | ||
* pour les fonctions mathématiques <code>'''rand sin exp log ...'''</ | * pour les fonctions mathématiques <span class="code">'''rand sin exp log ...'''</span> | ||
* pour la gestion du temps <code>'''systime strftime'''</ | * pour la gestion du temps <span class="code">'''systime strftime'''</span> | ||
Les possibilités de formatage importantes à votre disposition sont définies dans la page de manuel de awk. | Les possibilités de formatage importantes à votre disposition sont définies dans la page de manuel de awk. | ||
Et vous pouvez également définir vos fonctions grâce à la syntaxe : <tt> function nom_de_fonction(param1, param2, var_locale_1, var_locale_2) {<br /> ...<br /> ...<br /> }<br /> </tt> ou <tt> func nom_de_fonction(param1, param2 var_locale_1, var_locale_2) {<br /> ...<br /> ...<br /> }<br /> </tt> La déclaration des variables locales est étrange. Celà vient du fait que awk n'était pas prévu pour permettre la créaton de fonctions. Il a donc été décidé, de les ajouter à la liste des paramètres, mais séparées par des espaces supplémentaires. L'appel de la fonction, lui, ne contient que les paramètres. '''Exemple''' : <code>nom_de_fonction(5,2)</ | Et vous pouvez également définir vos fonctions grâce à la syntaxe : <tt> function nom_de_fonction(param1, param2, var_locale_1, var_locale_2) {<br /> ...<br /> ...<br /> }<br /> </tt> ou <tt> func nom_de_fonction(param1, param2 var_locale_1, var_locale_2) {<br /> ...<br /> ...<br /> }<br /> </tt> La déclaration des variables locales est étrange. Celà vient du fait que awk n'était pas prévu pour permettre la créaton de fonctions. Il a donc été décidé, de les ajouter à la liste des paramètres, mais séparées par des espaces supplémentaires. L'appel de la fonction, lui, ne contient que les paramètres. '''Exemple''' : <span class="code">nom_de_fonction(5,2)</span>. Les variables locales sont initialisées à chaine vide ou zéro. | ||
=== Blocs === | === Blocs === | ||
Ligne 131 : | Ligne 133 : | ||
'''Explications :''' | '''Explications :''' | ||
* Le bloc BEGIN est exécuté '''une fois''' au début, '''avant le traitement des données'''. Il affiche "début du script", initialise les variables et affiche le contenu de <code>maVariable</ | * Le bloc BEGIN est exécuté '''une fois''' au début, '''avant le traitement des données'''. Il affiche "début du script", initialise les variables et affiche le contenu de <span class="code">maVariable</span>. | ||
* Un bloc qui est exécuté pour chaque ligne commençant par un # ou vide, il incrémente la variable nbCommentaires. | * Un bloc qui est exécuté pour chaque ligne commençant par un # ou vide, il incrémente la variable nbCommentaires. | ||
* Un Bloc exécuté pour toutes les lignes, il incrémente la variable <code>nbLignes</ | * Un Bloc exécuté pour toutes les lignes, il incrémente la variable <span class="code">nbLignes</span>, affiche la ligne préfixée par son numéro. Le numéro est formaté pour occupper quattre caractères. Les lignes suivantes (à partir du <span class="code">if</span>) montrent l'équivalence entre le bloc du dessus et un test if dans le bloc principal. La variable nbCommentaires2 est incrémentée pour chaque ligne commençant par un dièse ou vide. | ||
* Le bloc END est exécuté '''une fois''' à la fin, '''après le traitement des données'''. On voit que la variable <code>maVariable</ | * Le bloc END est exécuté '''une fois''' à la fin, '''après le traitement des données'''. On voit que la variable <span class="code">maVariable</span> est toujours définie et que son contenu n'a pas été altéré. Il indique également le nombre total de lignes traitées et le nombre de lignes de commentaires ou vides présentes dans le fichier. | ||
'''Exécution''' | '''Exécution''' | ||
Ligne 144 : | Ligne 146 : | ||
awk permet, bien sûr l'utilisation de : | awk permet, bien sûr l'utilisation de : | ||
* <code>if (condition) {instructions} else {instructions}</ | * <span class="code">if (condition) {instructions} else {instructions}</span> : si alors sinon | ||
* <code>while (condition) {instructions}</ | * <span class="code">while (condition) {instructions}</span> : boucle tantque | ||
* <code>do {instructions} while ()</ | * <span class="code">do {instructions} while ()</span> : boucle répéter jusqu'à | ||
* <code>for (expr1; expr2; expr3) {instructions}</ | * <span class="code">for (expr1; expr2; expr3) {instructions}</span> : boucle pour | ||
* <code>for (var in tableau)</ | * <span class="code">for (var in tableau)</span> : boucle pour toutes les valeur du tableau. Il existe aussi while (var in tableau), if (var in tableau) ... | ||
== Utiliser awk == | == Utiliser awk == | ||
Ligne 154 : | Ligne 156 : | ||
=== Par la ligne de commande === | === Par la ligne de commande === | ||
Il existe deux façons de se servir de awk en tapant la commande awk suivie d'arguments.<br />'''La première''' consiste à saisir le code à exécuter directement sur la ligne de commande, celà convient parfaitement à de petits scripts. La syntaxe est la suivante : <code>awk -F séparateur_de_champ 'script awk' fichiers_a_traiter</ | Il existe deux façons de se servir de awk en tapant la commande awk suivie d'arguments.<br />'''La première''' consiste à saisir le code à exécuter directement sur la ligne de commande, celà convient parfaitement à de petits scripts. La syntaxe est la suivante : <span class="code">awk -F séparateur_de_champ 'script awk' fichiers_a_traiter</span>, par exemple <span class="code">awk -F ':' '{ print $1 " est " $5 }' /etc/passwd</span>.<br />'''La seconde''' consiste à placer votre script dans un fichier et à l'appeler par la commande <span class="code">awk -f fichier_script fichier_a_traiter</span> vous pouvez définir le séparateur de champ dans votre script, dans la section BEGIN ou le spécifier sur la ligne de commande grâce à l'option -F, comme ci-dessus. L'exemple ci-dessus deviendrait alors <span class="code">awk -f monScript /etc/passwd</span> ou monScript serait : <tt>BEGIN { FS = ":"}<br /> { print $1 " est " $5 }<br /> </tt> '''ou bien''' <span class="code">awk -F ':' -f monScript2 /etc/passwd</span> avec monScript2 valant <tt>{ print $1 " est " $5 }<br /> </tt> '''Notez''' que dans le script on doit saisir FS = ":" et non FS = ':'. '''Vous êtes prévenus !''' | ||
Comme d'habitude, pour les autres options de la ligne de commande, je vous renvoie à la page de manuel de awk. | Comme d'habitude, pour les autres options de la ligne de commande, je vous renvoie à la page de manuel de awk. | ||
Ligne 160 : | Ligne 162 : | ||
=== Des scripts exécutables === | === Des scripts exécutables === | ||
Cette utilisation a vite quelque chose de rébarbatif quand même, on rend donc les scripts directement exécutables en plaçant en en-tête <code><nowiki>#!/bin/awk -f</nowiki></ | Cette utilisation a vite quelque chose de rébarbatif quand même, on rend donc les scripts directement exécutables en plaçant en en-tête <span class="code"><nowiki>#!/bin/awk -f</nowiki></span> (le chemin doit être adapté à l'emplacement de awk). Créons donc un fichier monScript3 dans lequel nous plaçons : <tt><nowiki>#!/bin/awk -f</nowiki><br /> BEGIN { FS = ":"}<br /> { print $1 " est " $5 }<br /> </tt> Suite à cette saisie, on rend le script exécutable par <span class="code">chmod a+x</span> et on exécute par <span class="code">./monScript3 /etc/passwd</span>. | ||
A partir de maintenant, j'estime que vous avez les bases nécessaires pour réaliser vos scripts, si awk vous intéresse. Je vais maintenant vous soumettre des exemples qui vous aideront à mieux comprendre les choses qui sont peut être restées obscures lors de cette présentation sommaire. N'oubliez pas que le bon réflexe en cas de problème reste de lire la doc. En cas de gros pépin, postez un message dans le forum de lea-linux, je ne suis jamais loin ou envoyez moi un mail (j'y réponds dès que j'ai le temps). | A partir de maintenant, j'estime que vous avez les bases nécessaires pour réaliser vos scripts, si awk vous intéresse. Je vais maintenant vous soumettre des exemples qui vous aideront à mieux comprendre les choses qui sont peut être restées obscures lors de cette présentation sommaire. N'oubliez pas que le bon réflexe en cas de problème reste de lire la doc. En cas de gros pépin, postez un message dans le forum de lea-linux, je ne suis jamais loin ou envoyez moi un mail (j'y réponds dès que j'ai le temps). | ||
Ligne 172 : | Ligne 174 : | ||
<tt><nowiki>#!/usr/bin/awk -f</nowiki><br /> !(/^#/ || /^$/) {<br /> split ($1, array_ip, ".")<br /> printf "%03d.%03d.%03d.%03d\t%s\t%s\n", array_ip[1], array_ip[2], array_ip[3], array_ip[4],$2,$3<br /> }<br /> </tt> | <tt><nowiki>#!/usr/bin/awk -f</nowiki><br /> !(/^#/ || /^$/) {<br /> split ($1, array_ip, ".")<br /> printf "%03d.%03d.%03d.%03d\t%s\t%s\n", array_ip[1], array_ip[2], array_ip[3], array_ip[4],$2,$3<br /> }<br /> </tt> | ||
La première ligne signifie qu'on va chercher awk dans le répertoire /usr/bin/, ce qui est le bon chemin pour ma slack.<br /> La deuxiême ligne signifie qu'on ne s'intéresse qu'aux lignes qui ne commencent pas par un # ni aux lignes vides. En effet en expressions régulières, <code>^</ | La première ligne signifie qu'on va chercher awk dans le répertoire /usr/bin/, ce qui est le bon chemin pour ma slack.<br /> La deuxiême ligne signifie qu'on ne s'intéresse qu'aux lignes qui ne commencent pas par un # ni aux lignes vides. En effet en expressions régulières, <span class="code">^</span> signifie <span class="code">début de chaîne</span> et <span class="code">$</span>, <span class="code">fin de chaîne</span>.<br /> Par la suite, on découpe le premier paramètre en prenant comme séparateur le <span class="code">.</span>, on stocke le résultat dans un tableau et on affiche les données du tableau en les formattant puis les autres champs (nom_de_machine et nom_de_machine.domaine). Le formatage %03d signifie qu'on affiche des entiers sur trois caractères et que s'il manque des caractères, on précède ceux qui existent avec des 0.<br /> Il faut rendre ce script exécutable : <span class="code">chmod a+x prepare_hosts.awk</span><br /> Puis, on l'appelle comme ça : <span class="code">./prepare_hosts.awk /etc/hosts > hosts.prepared</span> pour récupérer le fichier traité dans un fichier hosts.prepared placé dans le répertoire en cours. | ||
=== Filtre de GéoConcept© vers GRASS(GNU/GPL) === | === Filtre de GéoConcept© vers GRASS(GNU/GPL) === | ||
Ligne 208 : | Ligne 210 : | ||
Nous allons ensemble analyser les morceaux intéressants de ce script. | Nous allons ensemble analyser les morceaux intéressants de ce script. | ||
Tout d'abord, je définis quelques variables dans la section BEGIN, qui restent accessibles jusqu'à la fin de l'exécution. Ensuite j'affecte des valeurs aux variables h1 et h2 en lisant les lignes dans un fichier du répertoire courant nommé <code>header</ | Tout d'abord, je définis quelques variables dans la section BEGIN, qui restent accessibles jusqu'à la fin de l'exécution. Ensuite j'affecte des valeurs aux variables h1 et h2 en lisant les lignes dans un fichier du répertoire courant nommé <span class="code">header</span>. | ||
Pour chaque objet (tout ce qui ne commence pas par //), j'insère un entête GRASS, composé des variables h1, h2, etc... que je range dans un fichier avec le symbole de redirection <code>></ | Pour chaque objet (tout ce qui ne commence pas par //), j'insère un entête GRASS, composé des variables h1, h2, etc... que je range dans un fichier avec le symbole de redirection <span class="code">></span> dont le chemin est la concaténation de la variable d'environnement LOCATION (<span class="code">ENVIRON["LOCATION"]</span>), de la chaîne <span class="code">"/dig_ascii/"</span> et de la première colonne de la ligne que je suis en train de lire (<span class="code">$1</span>).<br /> | ||
J'ajoute à la variable <code>melange</ | J'ajoute à la variable <span class="code">melange</span> une chaine constituée de ma variable <span class="code">$1</span> et de la chaine <span class="code">".awkImport"</span><br /> | ||
Je dis que les coordonnées de l'objet commencent à la colonne 5. Ceci me dit (voir explications du format GC ci-dessus) que si il y a des trous, c'est à dire si la somme du double du nombre de points intermédiaires donné en colonne 7 (<code>$(beginObj+2)</ | Je dis que les coordonnées de l'objet commencent à la colonne 5. Ceci me dit (voir explications du format GC ci-dessus) que si il y a des trous, c'est à dire si la somme du double du nombre de points intermédiaires donné en colonne 7 (<span class="code">$(beginObj+2)</span> équivaut à <span class="code">$7</span> car <span class="code">beginObj</span> vaut 5)+ les cinq colonnes de départ + 2 pour les 2 premières coordonnées absolues est inférieur au nombre total de colonnes de la ligne contenu dans la variable <span class="code">NF</span>, alors leur nombre me serra donné à la colonne suivant ce nombre de colonnes précalculé, d'où le <span class="code">nbTrous = $((beginObj+3) + $(beginObj+2) * 2)</span>. | ||
Ensuite la reconstitution des coordonnées absolues et l'inscription dans le fichier utilisé plus haut donne l'occasion de voir la syntaxe des boucles <code>for</ | Ensuite la reconstitution des coordonnées absolues et l'inscription dans le fichier utilisé plus haut donne l'occasion de voir la syntaxe des boucles <span class="code">for</span>. | ||
On flushe le buffer du fichier et on le ferme. Au début j'avais oublié, alors, je me suis retrouvé avec BEAUCOUP TROP de fichiers ouverts. Vous me direz, le fichier je l'ai pas ouvert. Bé, oui, c'est le côté expérimentation hasardeuse qui m'a fait comprendre. M'enfin, vous pourrez pas dire : "euh, ben, je comprends pas pourquoi mon linux me dit que le script il doit s'arrêter à cause que j'ai un problème de ressources".<br /> Ceci dit, je peux me tromper aussi. Si j'ai tort envoyez moi un mail. | On flushe le buffer du fichier et on le ferme. Au début j'avais oublié, alors, je me suis retrouvé avec BEAUCOUP TROP de fichiers ouverts. Vous me direz, le fichier je l'ai pas ouvert. Bé, oui, c'est le côté expérimentation hasardeuse qui m'a fait comprendre. M'enfin, vous pourrez pas dire : "euh, ben, je comprends pas pourquoi mon linux me dit que le script il doit s'arrêter à cause que j'ai un problème de ressources".<br /> Ceci dit, je peux me tromper aussi. Si j'ai tort envoyez moi un mail. | ||
Ligne 231 : | Ligne 233 : | ||
a+<br /> Xavier | a+<br /> Xavier | ||
<br/> | |||
<br/> | |||
'''<b>[[Dev-index|@ Retour à la rubrique Développement]]</b>''' | |||
<br/> | |||
<div class="merci">Cette page est issue de la documentation 'pré-wiki' de Léa a été convertie avec HTML::WikiConverter. Elle fut créée par Xavier GARREAU le 18/04/2001.</div> | <div class="merci">Cette page est issue de la documentation 'pré-wiki' de Léa a été convertie avec HTML::WikiConverter. Elle fut créée par Xavier GARREAU le 18/04/2001.</div> |
Dernière version du 1 décembre 2023 à 18:44
Introduction à (g)awk
Introduction
Présentation
Vous vous demandez surement pourquoi ce (g) avant awk, non ? Ce n'est pas le g de GNOME, ni celui de gtk+ mais celui de GNU. Extrait de la page de manuel (man gawk ou man awk) :
"Gawk est l'implémentation du projet GNU du langage de programmation AWK, lequel se conforme à la définition du langage dans la norme POSIX1003.2 Command Language and Utilities Standard. Cette version est basée, elle, sur la description d'AWK de Aho, Kernighan and Weinberger, plus les spécificités supplémentaires se trouvant dans la version de awk de l'UNIX System V version 4. Gawk ajoute également des extensions plus récentes des Laboratoires Bell ainsi que des extensions spécifiques à GNU."
Je ne mets pas ce pavé pour remplir de l'espace mais pour que les personnes à la base de choses que je ne fais que présenter soient reconnues pour leur travail et que l'on sache de quoi on parle.
Nous allons pouvoir commencer:
Qu'est ce que c'est
awk (j'utiliserai awk par la suite, mais c'est de gawk que l'on parle) est un langage interprété, installé par défaut sur les distributions linux (au moins rh et mandrake) dans le répertoire /bin. Il permet de traiter des fichiers de données structurées.
Un programme awk est une suite de blocs de code compris entre {} (c'est pas très original) qui sont appliqués aux lignes (par défaut) de données du fichier si un "modèle" est vérifié. La page de manuel nous donne les différents types de modèles utilisables :
BEGIN | Le bloc qui suit est exécuté au début du programme, une seule fois. | |
END | Le bloc qui suit est exécuté à la fin du programme, une seule fois. | |
/expression régulière/ | Le bloc qui suit est exécuté si les données en cours de traitement correspondent à l'expression régulière. Si vous ne connaissez pas les expressions régulières, lisez la page man egrep. Un exemple : /^#/ Permet de selectionner les lignes commençant par un #. | |
expression relationnelle | Le bloc qui suit est exécuté si l'expression est vraie. Un exemple : maVariable==5 | |
modèle && modèle | Le bloc qui suit est exécuté si les deux modèles sont vérifiés. | |
modèle | modèle | Le bloc qui suit est exécuté si au moins un des modèles est vérifié. |
modèle0 ? modèle1 : modèle2 | Le bloc qui suit est exécuté si les modèles 0 et 1 sont vérifiés ou si le modèle2 uniquement est vérifié. | |
(modèle) | Permet de grouper les modèles. Un exemple : (modèle0 |
modèle1) && modèle2 |
! modèle | Le bloc qui suit est exécuté si le modèle n'est pas vérifié. | |
modèle1, modèle2 | Le bloc qui suit est exécuté pour la partie des données en cours commençant par modèle1 et finissant par modèle2. Un exemple : /\/\*/, /\*\// pour afficher les commentaires multilignes |
Premiers pas
Les enregistrements
Pour awk, les blocs de données lues sont séparés par un caractère contenu dans la variable RS (Record Separator, retour à la ligne par défaut). On peut accéder à l'intégralité du bloc grâce à la variable $0. C'est ce qu'on appelle un enregistrement.
Un exemple : Tapez awk '{ print $0 }' un_fichier
Passionnant, non ? vous venez de réinventer la commande cat ;-) !
Un autre exemple : Tapez awk '/\/\*/, /\*\// { print $0 }' un_fichier_c_avec_des_commentaires
C'est déjà plus sympa, hein ?
Un dernier exemple : Tapez awk '! (/^ *#/ || /^$/) { print $0 }' un_fichier_conf
Vous n'aviez jamais vu votre fichier de config d'Apache comme ça, si ?
Les champs
Ah, la nature ... Courir nus dans les champs en se tenant la main ... NON, je vous arrête tout de suite.
Chaque enregistrement est séparé en champs par un caractère contenu dans la variable FS (Field Separator, un espace par défaut). Les champs sont accessibles par les variables $1, $2, $3, etc ...
Pour afficher le 3ème champ de toutes les lignes de vos données à traiter, vous utilisez print $3.
Un exemple : awk -F ':' '{ print $1 " est " $5 }' /etc/passwd pour afficher la description des utilisateurs de votre machine.
Les variables
Il existe bon nombre de variables prédéfinies en awk. Je vais vous en donner la liste résumée, encore une fois, je n'invente rien et je ne créée rien, je ne fais que vous refaire une lecture de la page de manuel. D'ailleurs pour avoir une liste exhaustive des variables, consultez la.
ARGC | Si vous avez fait du c, vous vous en doutez ! Sinon, je vous le dis. Cette variable contient le nombre d'arguments passés au programme. (sans les options passées à awk) |
ARGV | Un tableau contenant les ARGC paramètres, indéxé de 0 à ARGC-1. Ce sont tous les fichiers à traiter |
CONVFMT | Le format par défaut pour l'affichage des nombres, "%.6g" par défaut |
ENVIRON | Un tableau contenant les variables d'environnement. Exemple ENVIRON["PATH"] contient votre path. |
FIELDWIDTHS | Pour le cas de traitement de données non délimités mais contenant des champs de largeur fixe. Cette variable est de la forme largeur_champ_1 largeur_champ_2 largeur_champ_3 etc.... |
FNR | grep ^45: permet d'afficher la ligne 45 du fichier un_fichier. On fait appel à grep, ce n'est plus du 100% awk mais c'est beau la diversité, non ? |
FS | Le séparateur de champs. No comment ! |
IGNORECASE | En gros, permet de ne pas prendre en compte la casse lors des comparaisons de chaines de caractères entre elles où avec des expressions régulières. Voir la page de manuel pour les détails. |
NF | Norme Française ... NON, Number of fields : C'est le nombre de champs de l'enregistrement en cours. |
NR | Number of Records : Le nombre d'enregistrements traités jusqu'à maintenant. |
RS | Le séparateur d'enregistrements. No comment ! |
Bien entendu, vous pouvez également définir les vôtres.
- Exemples :
- maVar=3 déclare et initialise la variable myVar à la valeur 3.
print maVar affiche le contenu de la variable maVar.
print $maVar affiche le champ n°maVar de l'enregistrement en cours (ici, équivaut à print $3).
print $(maVar-1) affiche le champ n°(maVar-1) de l'enregistrement en cours (ici, équivaut à print $2).
split("toto:tata:titi:tutu", arr, ":") initialise le tableau arr à ("toto","tata",...). On accède ensuite aux valeurs par arr[1], arr[2], ..., arr[4]
Les fonctions
Il y en a de nombreuses :
- pour l'affichage formaté, de textes et de nombres. print printf ...
- pour les opérations habituelles sur les chaines de caractères, concaténation, substitution, remplacement, recherche ... gensub substr tolower index split ...
- pour les accés aux systèmes de fichiers et au système fflush close nextline system
- pour les fonctions mathématiques rand sin exp log ...
- pour la gestion du temps systime strftime
Les possibilités de formatage importantes à votre disposition sont définies dans la page de manuel de awk.
Et vous pouvez également définir vos fonctions grâce à la syntaxe : function nom_de_fonction(param1, param2, var_locale_1, var_locale_2) {
...
...
}
ou func nom_de_fonction(param1, param2 var_locale_1, var_locale_2) {
...
...
}
La déclaration des variables locales est étrange. Celà vient du fait que awk n'était pas prévu pour permettre la créaton de fonctions. Il a donc été décidé, de les ajouter à la liste des paramètres, mais séparées par des espaces supplémentaires. L'appel de la fonction, lui, ne contient que les paramètres. Exemple : nom_de_fonction(5,2). Les variables locales sont initialisées à chaine vide ou zéro.
Blocs
Merci à Jean-Louis pour m'avoir montré que je n'insistais pas assez sur le fonctionnement des blocs. C'est grâce à lui que cette partie existe
Utilisation des blocs
Prenons un exemple :
#!/usr/bin/awk -f
BEGIN {
print "début du script"
maVariable=5
nbLignes=0
nbCommentaires=0
nbCommentaires2=0
print "maVariable vaut " maVariable
}
( /^#/ || /^$/ ) {
nbCommentaires++
}
{
nbLignes++
printf "%04d:%s\n",FNR,$0
if ( $0 ~ /^#/ || $0 ~ /^$/ ) {
nbCommentaires2++
}
}
END {
print "fin du script maVariable vaut toujours " maVariable
print "Le script a traité " FNR " lignes dont " nbCommentaires "(=" nbCommentaires2 ") lignes vides ou commençant par un #"
}
Explications :
- Le bloc BEGIN est exécuté une fois au début, avant le traitement des données. Il affiche "début du script", initialise les variables et affiche le contenu de maVariable.
- Un bloc qui est exécuté pour chaque ligne commençant par un # ou vide, il incrémente la variable nbCommentaires.
- Un Bloc exécuté pour toutes les lignes, il incrémente la variable nbLignes, affiche la ligne préfixée par son numéro. Le numéro est formaté pour occupper quattre caractères. Les lignes suivantes (à partir du if) montrent l'équivalence entre le bloc du dessus et un test if dans le bloc principal. La variable nbCommentaires2 est incrémentée pour chaque ligne commençant par un dièse ou vide.
- Le bloc END est exécuté une fois à la fin, après le traitement des données. On voit que la variable maVariable est toujours définie et que son contenu n'a pas été altéré. Il indique également le nombre total de lignes traitées et le nombre de lignes de commentaires ou vides présentes dans le fichier.
Exécution
# chmod a+x ceScript.awk
# ./ceScript.awk unfichier.shell
début du script
maVariable vaut 5
0001:# Ce script affiche hello world
0002:
0003:echo "hello world"
0004:exit 0
fin du script maVariable vaut toujours 5
Le script a traité 4 lignes dont 2(=2) lignes vides ou commençant par un #
Divers
awk permet, bien sûr l'utilisation de :
- if (condition) {instructions} else {instructions} : si alors sinon
- while (condition) {instructions} : boucle tantque
- do {instructions} while () : boucle répéter jusqu'à
- for (expr1; expr2; expr3) {instructions} : boucle pour
- for (var in tableau) : boucle pour toutes les valeur du tableau. Il existe aussi while (var in tableau), if (var in tableau) ...
Utiliser awk
Par la ligne de commande
Il existe deux façons de se servir de awk en tapant la commande awk suivie d'arguments.
La première consiste à saisir le code à exécuter directement sur la ligne de commande, celà convient parfaitement à de petits scripts. La syntaxe est la suivante : awk -F séparateur_de_champ 'script awk' fichiers_a_traiter, par exemple awk -F ':' '{ print $1 " est " $5 }' /etc/passwd.
La seconde consiste à placer votre script dans un fichier et à l'appeler par la commande awk -f fichier_script fichier_a_traiter vous pouvez définir le séparateur de champ dans votre script, dans la section BEGIN ou le spécifier sur la ligne de commande grâce à l'option -F, comme ci-dessus. L'exemple ci-dessus deviendrait alors awk -f monScript /etc/passwd ou monScript serait : BEGIN { FS = ":"}
{ print $1 " est " $5 }
ou bien awk -F ':' -f monScript2 /etc/passwd avec monScript2 valant { print $1 " est " $5 }
Notez que dans le script on doit saisir FS = ":" et non FS = ':'. Vous êtes prévenus !
Comme d'habitude, pour les autres options de la ligne de commande, je vous renvoie à la page de manuel de awk.
Des scripts exécutables
Cette utilisation a vite quelque chose de rébarbatif quand même, on rend donc les scripts directement exécutables en plaçant en en-tête #!/bin/awk -f (le chemin doit être adapté à l'emplacement de awk). Créons donc un fichier monScript3 dans lequel nous plaçons : #!/bin/awk -f
BEGIN { FS = ":"}
{ print $1 " est " $5 }
Suite à cette saisie, on rend le script exécutable par chmod a+x et on exécute par ./monScript3 /etc/passwd.
A partir de maintenant, j'estime que vous avez les bases nécessaires pour réaliser vos scripts, si awk vous intéresse. Je vais maintenant vous soumettre des exemples qui vous aideront à mieux comprendre les choses qui sont peut être restées obscures lors de cette présentation sommaire. N'oubliez pas que le bon réflexe en cas de problème reste de lire la doc. En cas de gros pépin, postez un message dans le forum de lea-linux, je ne suis jamais loin ou envoyez moi un mail (j'y réponds dès que j'ai le temps).
Des filtres en exemple
Transformer le fichier hosts
Si vous êtes des habitués du site, ce premier exemple vous rapellera peut être quelque chose puisque je l'avais posté dans le forum en réponse à une personne qui souhaitait adapter le format des IP du fichier hosts à ses besoins, xxx.xxx.xxx.xxx.
Voici donc le code du premier filtre exemple :
#!/usr/bin/awk -f
!(/^#/ || /^$/) {
split ($1, array_ip, ".")
printf "%03d.%03d.%03d.%03d\t%s\t%s\n", array_ip[1], array_ip[2], array_ip[3], array_ip[4],$2,$3
}
La première ligne signifie qu'on va chercher awk dans le répertoire /usr/bin/, ce qui est le bon chemin pour ma slack.
La deuxiême ligne signifie qu'on ne s'intéresse qu'aux lignes qui ne commencent pas par un # ni aux lignes vides. En effet en expressions régulières, ^ signifie début de chaîne et $, fin de chaîne.
Par la suite, on découpe le premier paramètre en prenant comme séparateur le ., on stocke le résultat dans un tableau et on affiche les données du tableau en les formattant puis les autres champs (nom_de_machine et nom_de_machine.domaine). Le formatage %03d signifie qu'on affiche des entiers sur trois caractères et que s'il manque des caractères, on précède ceux qui existent avec des 0.
Il faut rendre ce script exécutable : chmod a+x prepare_hosts.awk
Puis, on l'appelle comme ça : ./prepare_hosts.awk /etc/hosts > hosts.prepared pour récupérer le fichier traité dans un fichier hosts.prepared placé dans le répertoire en cours.
Filtre de GéoConcept© vers GRASS(GNU/GPL)
GéoConcept et GRASS sont deux systèmes d'information géographique (je dirais SIG par la suite). GéoConcept permet d'exporter des objets surfaces selon une syntaxe explicitée ci-dessous. Le fichier export de GéoConcept contient tous les objets exportés.
Bien entendu ce format n'est pas du tout celui de GRASS qui est explicité ci-après, j'ai donc du écrire un p'tit filtre. Il n'est pas universel puisqu'on ne sait pas combien de champs se trouvent avant les coordonnées avant de faire l'export dans GéoConcept. Je le donne ici à titre d'exemple et j'y ai fait de grosses découpes, ne l'utilisez pas tel quel. Si vous êtes arrivés ici en faisant une recherche sur les SIG et que ce script vous intéresse, je peux vous l'envoyer par mail.
Format GéoConcept :
//$SYSCOORD {Type:1}
//$UNIT Distance:m
//$FORMAT 1
28878 buffer comm TRITTELING 911561.09 2462669.95 69 0. 1.e-002 ...
28595 buffer comm LONGWY 848138.94 2509824.85 91 45.62 -171.88 ...
...
Soit en clair :
- 3 lignes inutiles pour nous.
- L'identifiant GéoConcept de l'objet
- Le type d'objet
- Le sous-type d'objet
- Les champs qu'on a demandé à exporter, ici le nom de l'objet et c'est tout
- Les coordonnées absolues x et y du premier point du contour de la surface
- Le nombre de points décrivant le contour
- Les coordonnées relatives entre chaque point
- Le nombre de trous
- Les coordonnées absolues x et y du premier point du contour du trou
- Le nombre de points décrivant le contour du trou
- Les coordonnées relatives entre chaque point du contour du trou
Format GRASS :
14 lignes d'en-tête comprenant des renseignements divers
...
A 69
2462669.95 911561.09
2462669.96 911561.09
...
Soit en clair :
- 11 lignes inutiles pour nous, recollées directement d'un header.
- A 25 : pour dire que c'est une surface (area) et que son contour comporte 25 points
- Les coordonnées absolues y et x des points du contour de la surface
- Les mêmes renseignements pour les trous
Les bouts intéressants du script BEGIN {
melange = "v.patch input="
exited = 0
getline h1 < "header"
getline h2 < "header"
...
}
!/\/\// {
print h1 > ENVIRON["LOCATION"] "/dig_ascii/" $1
print h2 > ENVIRON["LOCATION"] "/dig_ascii/" $1
...
melange = melange $1 ".awkImport,"
...
beginObj = 5
if ( (beginObj + 2 + $(beginObj+2) * 2) < NF) {
nbTrou = $((beginObj+3) + $(beginObj+2) * 2)
}
else {
nbTrou = 0
}
for ( i=0 ; i < nbTrou+1 ; i++) {
actualx = $beginObj
actualy = $(beginObj+1)
print "A " ($(beginObj+2)+2) > ENVIRON["LOCATION"] "/dig_ascii/" $1
print " " actualy " " actualx > ENVIRON["LOCATION"] "/dig_ascii/" $1
for ( j=(beginObj+3) ; j<(beginObj+3)+$(beginObj+2)*2-1 ; j++ ) {
actualx += $j
actualy += $(++j)
print " " actualy " " actualx > ENVIRON["LOCATION"] "/dig_ascii/" $1
}
print " " $(beginObj+1) " " $beginObj > ENVIRON["LOCATION"] "/dig_ascii/" $1
...
}
...
fflush (ENVIRON["LOCATION"] "/dig_ascii/" $1)
close (ENVIRON["LOCATION"] "/dig_ascii/" $1)
...
if ( system("v.support map=" $1 ".awkImport option=build -s threshold=" thresh " >/dev/null")!=0 )
exit (1)
}
END {
print "Patching all files into composite.awkImport"
if ( system (melange " output=composite.awkImport" ) != 0 )
exit (1)
...
for (i=1;i<=FNR;i++) {
getline < ARGV[1]
print i ":" $4 > ENVIRON["LOCATION"] "/dig_cats/" mapName
}
system (effaceTempos " > /dev/null")
fflush ()
...
}
Nous allons ensemble analyser les morceaux intéressants de ce script.
Tout d'abord, je définis quelques variables dans la section BEGIN, qui restent accessibles jusqu'à la fin de l'exécution. Ensuite j'affecte des valeurs aux variables h1 et h2 en lisant les lignes dans un fichier du répertoire courant nommé header.
Pour chaque objet (tout ce qui ne commence pas par //), j'insère un entête GRASS, composé des variables h1, h2, etc... que je range dans un fichier avec le symbole de redirection > dont le chemin est la concaténation de la variable d'environnement LOCATION (ENVIRON["LOCATION"]), de la chaîne "/dig_ascii/" et de la première colonne de la ligne que je suis en train de lire ($1).
J'ajoute à la variable melange une chaine constituée de ma variable $1 et de la chaine ".awkImport"
Je dis que les coordonnées de l'objet commencent à la colonne 5. Ceci me dit (voir explications du format GC ci-dessus) que si il y a des trous, c'est à dire si la somme du double du nombre de points intermédiaires donné en colonne 7 ($(beginObj+2) équivaut à $7 car beginObj vaut 5)+ les cinq colonnes de départ + 2 pour les 2 premières coordonnées absolues est inférieur au nombre total de colonnes de la ligne contenu dans la variable NF, alors leur nombre me serra donné à la colonne suivant ce nombre de colonnes précalculé, d'où le nbTrous = $((beginObj+3) + $(beginObj+2) * 2).
Ensuite la reconstitution des coordonnées absolues et l'inscription dans le fichier utilisé plus haut donne l'occasion de voir la syntaxe des boucles for.
On flushe le buffer du fichier et on le ferme. Au début j'avais oublié, alors, je me suis retrouvé avec BEAUCOUP TROP de fichiers ouverts. Vous me direz, le fichier je l'ai pas ouvert. Bé, oui, c'est le côté expérimentation hasardeuse qui m'a fait comprendre. M'enfin, vous pourrez pas dire : "euh, ben, je comprends pas pourquoi mon linux me dit que le script il doit s'arrêter à cause que j'ai un problème de ressources".
Ceci dit, je peux me tromper aussi. Si j'ai tort envoyez moi un mail.
La ligne suivante nous donne un exemple d'utilisation de la commande system, avec récupération du code de retour et aussi de la commande exit. RAS.
La suite montre l'utilisation de variables telles que FNR, ARGV ainsi que des appels à la fonction système avec redirection et l'utilisation des variables déclarées dans le begin et que l'on a fait évoluer au cours du script.
Le mot de la fin, enfin ;-)
Voilà, j'espère vous avoir fait comprendre ce qu'était awk et à quoi il servait. Bien sûr on peut faire la même chose en Perl, PHP, C, Java. Je sais. Mais utiliser awk plutôt que Perl quand il est adapté c'est un peu comme aller dans un petit hôtel/restaurant pittoresque plutôt que d'aller au Club Med. En plus, awk est très léger et peut être un allié puissant dans les systèmes embarqués.
Nota : Je vous ai généré un pdf du manuel de awk accessible [awk.pdf ici] (64ko). Petit rappel en passant : la manipulation pour générer des pdf à partir des pages de manuel se trouve sur Léa section [../trucs Trucs et astuces], rubrique [/trucs/sommaire.php3?cat=6 Shell].
a+
Xavier
@ Retour à la rubrique Développement
Copyright
Copyright © 18/04/2001, Xavier GARREAU
![]() ![]() ![]() ![]() |
Ce document est publié sous licence Creative Commons Attribution, Partage à l'identique, Contexte non commercial 2.0 : http://creativecommons.org/licenses/by-nc-sa/2.0/fr/ |