Next revision | Previous revision |
informatique:linux:awk [2010/04/28 14:50] – créée pteu | informatique:linux:awk [2025/02/17 14:14] (current) – [Exemples] case-insensitive search pteu |
---|
{{tag>langage programmation}} | |
| |
| |
======awk====== | ======awk====== |
| |
C'est une commande (un langage) de manipulation de chaînes de caractères d'un fichier ou d'un flux en entrée. | C'est un langage de manipulation de chaînes de caractères d'un fichier ou d'un flux en entrée. |
| |
Les différentes commandes que l'on peut passer sont de la forme **motif { action }**. | Les différentes commandes que l'on peut passer sont de la forme **motif { action }**. |
''awk'' découpe le flux de caractère par ligne (enregistrement) et colonne (champ) ; on peut récupérer les colonnes avec $1, $2,... ,$NF (dernière colonne) et $0 qui correspond à la ligne complète. Les champs peuvent etre specifiés par des expressions, comme $(NF-1) pour l'avant dernier champs. | **awk** traite séquentiellement le flux de caractères (fichier en paramètre ou flux en entrée) par ligne (enregistrement) et colonne (champ) ; on peut récupérer les colonnes avec les variables $1, $2, .., $10 ,$NF (dernière colonne) et $0 qui correspond à la ligne complète. Les champs peuvent être spécifiés par des expressions, comme $(NF-1) pour l'avant dernier champs. |
| |
====Le motif==== | ====Le motif==== |
| |
* une expression régulière : "/regexp/" ; "expression ~ /regexp/" ou sa négation "expression !~ /regexp/" | * une expression régulière : "/regexp/" ; "expression ~ /regexp/" ou sa négation "expression !~ /regexp/" |
* BEGIN ou END | Pour utiliser une variable bash comme regexp avec awk, on peut procéder ainsi : |
| <code> |
| awk '$1 ~ /^'$VAR'/ {print}' |
| </code> |
| * BEGIN ou END (BEGIN permet de faire des actions avant le traitement du fichier ; END permet de les faire à la fin du traitement) |
* une comparaison (<, >, =>, !=, ...) | * une comparaison (<, >, =>, !=, ...) |
* une combianison des 3 séparés d'un opérateur booléen (|| ou &&) | * une combinaison des 3 séparés d'un opérateur booléen (|| ou &&) |
| |
====Les options==== | ====Les options==== |
| |
* ''-f <fichier>'' la commande s'applique à un fichier | * ''-f <fichier>'' précise un fichier de script awk |
| <code bash> |
| vi test.awk |
| BEGIN{FS=";"; OFS=" : "} |
| $0 ~ "test" {print $0} |
| :x |
| |
| awk -f test.awk fichier.txt |
| ceci est un test |
| </code> |
* ''-F "<séparateur>"'' spécifie le séparateur de champs (" " (espace) par défaut). NB : on peut spécifier plusieurs séparateurs en les séparant par "|" : | * ''-F "<séparateur>"'' spécifie le séparateur de champs (" " (espace) par défaut). NB : on peut spécifier plusieurs séparateurs en les séparant par "|" : |
<code> | <code bash> |
echo "toto;tata-titi" | awk -F":|-" '{print $1" "$2" "$3}' | echo "toto;tata-titi" | awk -F":|-" '{print $1" "$2" "$3}' |
toto tata titi | toto tata titi |
| </code> |
| * ''-v var="val"'' : permet d'affecter une valeur //val// à une variable //var// avant la partie BEGIN |
| <code bash> |
| HOST=toto ; echo titi | awk -v h=$HOST '{print "h="h}' |
| h=toto |
| </code> |
| |
| Autre façon de passer des variables dans un fichier de script : |
| <code bash> |
| vi test.awk |
| BEGIN{FS=";"; OFS=" : "} |
| $0 ~ rechch {print $0} |
| :x |
| |
| awk -f test.awk rechch=test fichier.txt |
| ceci est un test |
</code> | </code> |
| |
====Les variables prédéfinies==== | ====Les variables prédéfinies==== |
| |
''<variable>'' <desc> <valeur par défaut> | ^ Variable ^ Description ^ Valeur par défaut ^ |
* ''ARGC'' Nombre d'arguments de la ligne de commande | | ARGC | nombre d'arguments de la ligne de commande || |
* ''ARGV'' tableau des arguments de la ligne de commnde | | ARGV | tableau des arguments de la ligne de commande || |
* ''FILENAME'' nom du fichier sur lequel on applique les commandes | | FILENAME | nom du fichier sur lequel on applique les commandes || |
* ''FNR'' Nombre d'enregistrements du fichier | | ENVIRON | tableau associatif des variables d'environnement || |
* ''FS'' separateur de champs en entrée | | FNR | nombre d'enregistrements parcourus du fichier courant || |
* ''NF'' nombre de champs de l'enregistrement courant | | FS | séparateur de champs (en entrée) | <tab>, <espace> ou <retour chariot> contigus | |
* ''NR'' nombre d'enregistrements deja lu | | NF | nombre de champs de l'enregistrement courant || |
* ''OFMT'' format de sortie des nombres | | NR | nombre d'enregistrements parcouru (tous fichiers confondus) || |
* ''OFS'' separateur de champs pour la sortie | | OFMT | format de sortie des nombres || |
* ''ORS'' separateur d'enregistrement pour la sortie | | OFS | séparateur de champs pour la sortie | <espace> | |
* ''RLENGTH'' longueur de la chaine trouvée | | ORS | séparateur d'enregistrement pour la sortie | <retour chariot> | |
* ''RS'' separateur d'enregistrement en entrée | | RLENGTH | longueur de la chaine trouvée || |
* ''RSTART'' debut de la chaine trouvée | | RS | séparateur d'enregistrement en entrée | <retour chariot> | |
* ''SUBSEP'' separateur de subscript | | RSTART | début de la chaine trouvée || |
| | SUBSEP | séparateur de subscript || |
| |
====Les fonctions==== | ====Les fonctions texte==== |
| |
Les paramètres sont soit des chaines de caractère (s et t), soit des regexp (r) soit des entiers (i et n). | Les paramètres sont soit des chaines de caractères (s et t), soit des regexp (r) soit des entiers (i et n). |
* ''gsub(r,s,t)'' : sur la chaine t, remplace toutes les occurance de r par s | * ''gsub(r,s,t)'' : sur la chaine t, remplace toutes les occurrence de r par s |
* ''index(s,t)'' : retourne la position la plus à gauche de la chaine t dans la chaine s | * ''index(s,t)'' : retourne la position la plus à gauche de la chaine t dans la chaine s |
* ''length(s)'' : retourne la longueur de la chaine s | * ''length(s)'' : retourne la longueur de la chaine s |
* ''match(s,r)'' : retourne l'index ou s correspond à r et positionne RSTART et RLENTH | * ''match(s,r)'' : retourne l'index ou s correspond à r et positionne RSTART et RLENTH |
* ''split(s,a,fs)'' : split s dans le tableau a sur fs, retourne le nombre de champs | * ''split(s,a,fs)'' : split s dans le tableau a sur fs, retourne le nombre de champs |
* ''sprintf(fmt,liste expressions)'' : retourne la liste des expressions formatée suivant fmt | * ''sprintf(fmt,liste expressions)'' : retourne la liste des expressions formatée suivant fmt |
* ''sub(r,s,t)'' : comme gsub, mais remplace uniquement la première occurrence | * ''sub(r,s,t)'' : comme gsub, mais remplace uniquement la première occurrence |
* ''substr(s,i,n)'' : retourne la sous chaine de s commençant en i et de taille n | * ''substr(s,i,n)'' : retourne la sous chaine de s commençant en i et de taille n |
| * ''tolower(s)'' : passer la chaîne en minuscules |
| * ''toupper(s)'' : passer la chaîne en majuscules |
| * ''count[s]'' : compte le nombre d’occurrence de s |
| |
| ===Le cas printf=== |
| |
| **printf** et ses dérivées sont des fonctions d'affichage de texte qui permettent de formater la sortie (pour un résultat bien léché comme on aime). Voici quelques cas d'utilisation commentés : |
| <code bash> |
| # afficher les logins et description des utilisateurs locaux qui utilisent le shell /bin/bash |
| # en alignant les logins à droite et sur 20 caractères |
| awk -F: '$7 ~ /\/bin\/bash/ {printf "%-20s %s\n", $1, $5}' /etc/passwd |
| root root |
| dude a dude |
| robert le gros robert |
| |
| # puisque l'affichage est par colonne, on va afficher les noms pour être plus parlant |
| # on utilise l'instruction BEGIN qui, comme on l'a vu, ne s'exécute qu'une fois en début de script |
| awk -F: 'BEGIN {printf "%-20s %s\n", "login:","description:"} $7 ~ /\/bin\/bash/ {printf "%-20s %s\n", $1, $5}' /etc/passwd |
| login: description: |
| root root |
| dude a dude |
| robert le gros robert |
| |
| # on peut même définir un format d'affichage pour ne pas avoir a le retaper à chaque printf : |
| awk -F: 'BEGIN {format = "%-20s %s\n"; printf format, "login:","description:"; printf format, "---","---"} $7 ~ /\/bin\/bash/ {printf format, $1, $5}' /etc/passwd |
| login: description: |
| --- --- |
| root root |
| dude a dude |
| robert le gros robert |
| |
| # la même commande, en plus lisible : |
| awk -F: 'BEGIN {format = "%-10s %s\n" |
| printf format, "login:","description:" |
| printf format, "---","---"} |
| $7 ~ /\/bin\/bash/ {printf format, $1, $5}' /etc/passwd |
| </code> |
| |
| ====Les fonctions mathématiques==== |
| |
| cos(x), exp(x), int(x), log(x), sin(x), sqrt(x), atan2(x,y), rand(x), srand(x) |
| |
====Exemples==== | ====Exemples==== |
| |
* imprime chaque ligne du fichier /etc/passwd après avoir effacé le deuxième champs | * imprime chaque ligne du fichier /etc/passwd après avoir effacé le deuxième champs |
<code> | <code bash> |
awk -F ":" '{ $2 = "" ; print $0 }' /etc/passwd | awk -F ":" '{ $2 = "" ; print $0 }' /etc/passwd |
</code> | </code> |
| |
* imprime le nombre total de lignes du fichiers | * imprime le nombre total de lignes du fichiers |
<code> | <code bash> |
awk 'END {print NR}' /etc/passwd | awk 'END {print NR}' /etc/passwd |
</code> | </code> |
| |
* lire la 3ème ligne d'un fichier : | * lire la 3ème ligne d'un fichier : |
<code> | <code bash> |
awk '{if (NR==3) print}' <fichier> | awk '{if (NR==3) print}' <fichier> |
</code> | </code> |
| |
* imprime le dernier champs de chaque ligne | * imprime le dernier champs de chaque ligne |
<code> | <code bash> |
awk '{print $NF}' /etc/passwd | awk '{print $NF}' /etc/passwd |
</code> | </code> |
| |
* imprime le login et le temps de connexion. | * imprime le login et le temps de connexion. |
<code> | <code bash> |
who | awk '{print $1,$5}' | who | awk '{print $1,$5}' |
</code> | </code> |
| |
* imprime les lignes de plus de 75 caractères. (''print'' équivaut à ''print $0'') | * imprime les lignes de plus de 75 caractères. (''print'' équivaut à ''print $0'') |
<code> | <code bash> |
awk 'length($0)>75 {print}' /etc/passwd | awk 'length($0)>75 {print}' /etc/passwd |
</code> | </code> |
| |
* tests sur le fichier /etc/passwd : | * tests sur le fichier /etc/passwd : |
<code> | <code bash> |
awk 'BEGIN { print "Verification du fichier /etc/passwd pour ..."; | awk 'BEGIN { print "Verification du fichier /etc/passwd pour ..."; |
print "- les utilisateurs avec UID = 0 " ; | print "- les utilisateurs avec UID = 0 " ; |
| |
* supprimer le suffixe du domaine d'un nom de machine : | * supprimer le suffixe du domaine d'un nom de machine : |
<code> | <code bash> |
echo "toto.domaine.fr est un nom trop long !" | awk 'gsub(/\..*$/,"",$1) {print "machine="$1}' | echo "toto.domaine.fr est un nom trop long !" | awk 'gsub(/\..*$/,"",$1) {print "machine="$1}' |
</code> | </code> |
| |
| * afficher les blocs de texte du fichier FIC.txt compris entre les balises BEGIN et END : |
| <code bash> |
| awk '/BEGIN/,/END/' FILE.txt |
| </code> |
| |
| * supprimer (ne pas afficher) les doublons de lignes dans un fichier : |
| <code bash> |
| awk '!x[$0]++' test.tmp |
| </code> |
| |
| * quitter la boucle awk après le premier match |
| <code bash> |
| echo -e "toto\ntoto\n" | awk '/toto/ {print; exit}' |
| toto |
| </code> |
| |
| * émuler la fonction trim (efface les espaces avant et après une chaine) : |
| <code bash> |
| echo -e "toto :titi\n tata:tutu" | \ |
| awk -F\: '{ sub(/^[ \t\r\n]+/, "", $1);sub(/[ \t\r\n]+$/, "", $1);print "\""$1"\""}' |
| </code> |
| |
| * ne pas afficher les lignes en doublon |
| <code bash> |
| # count s'incrémente à chaque ligne déjà rencontrée ; on n'affiche donc la ligne qu'à sa première apparition |
| awk '!(count[$0]++)' fic.txt |
| </code> |
| |
| * compter le nombre de caractère dans une ligne (k pour l'exemple) |
| <code bash> |
| echo $ligne | awk -F'k' '{print NF-1}' |
| </code> |
| |
| * N'afficher que les lignes paires |
| <code bash> |
| awk 'NR%2==0' fic.txt |
| </code> |
| |
| * filtrer sans tenir compte de la casse (minuscule/majuscule = //case insensitive//): |
| <code bash> |
| echo "TOTO" | awk 'BEGIN {IGNORECASE=1} /toto/ {print}' |
| TOTO |
| </code> |