
Cet article traite des outils puissants utilisables dans un shell (bash ou autre) que sont grep, sed, awk et find. L'étude de ces outils met en avant un concept important du shell que sont les expressions régulières. Nous commencerons par faire l'étude de base de ces expressions régulières avant d'aller plus avant avec ces outils.
Cet article traite des outils puissants utilisables dans un shell (bash ou autre) que sont grep, sed, awk et find. L'étude de ces outils met en avant un concept important du shell que sont les expressions régulières. Nous commencerons par faire l'étude de base de ces expressions régulières avant d'aller plus avant avec ces outils.
La notion d'expressions régulières est utilisée en shell mais aussi avec des éditeurs de texte comme ed, vi et aussi avec d'autres langages comme perl et php.
Qu'est-ce qu'une expression régulière?
Une expression régulière est une chaîne de caractères ASCII servant de modèle (de filtre) pour manipuler d'autres chaînes de caractères. Ce modèle sert de calque pour trouver des portions de chaînes dans une ou plusieurs chaînes de caractères.
Une expression régulière permet, entre autres, de retrouver un mot, ou une phrase (ou tout autre chose) dans un texte, ressemblant au modèle que l'on a construit...
L'écriture d'une expression régulière a pour objectif de rechercher une ou plusieurs occurrences de texte correspondants à ce que nous recherchons. Une occurrence est ici une suite de caractères qui répond au modèle décrit par l'expression régulière.
L'expression régulière est constituée d'une suite de caractères spéciaux et normaux permettant de décrire les occurrences à trouver.
Les caractères spéciaux (ou symboles) sont ici un certains nombres de caractères qui ont une signification particulière dans l'interprétation de l'expression régulière.
Nous allons commencer par les décrire en les regroupant par thème correspondant à un type d'action.
Le début de ligne est décrit par le caractère "^".
La fin de ligne est décrite par le caractère "$".
exemples:
L'expression ^string décrit une ligne commençant par la chaine de caractères "string".
L'expression string$ décrit une ligne se terminant par la chaîne de caractère "string".
Les crochets [] permettent de déterminer des choix de caractères.
exemples:
Il y a moyen d'indiquer une négation dans les crochets pour signifier "tout sauf" avec le caractère "^" juste après le crochet ouvrant (ne pas confondre avec la description précédente qui indiquait un début de ligne).
exemples:
"." permet de caractériser n'importe quel caractère.
Exemples:
"abc+" chaîne qui contient "ab" suivie de un ou plusieurs "c" ("abc", "abcc", ...)
"abc*" chaîne qui contient "ab" suivie de zéro ou plusieurs "c" ("ab", "abc", ...)
"abc?" chaîne qui contient "ab" suivie de zero ou un "c" ("ab" ou "abc")
"abc{3}" chaîne qui contient "ab" suivi de 3 "c".
"a{1,3}" signifie "a", "aa" ou "aaa".
Petit exercice:
Ecrire les équivalents de "*", "+" et "?" avec la notation "{}".
La notion de la détermination d'une répétition, existence ou autre, peut être étendu du caractère à une séquence de caractères en les regroupant dans des parenthèses ().
Exemples:
"ab(cd)+" signifie "abcd", "abcdcd", "abcdcdcd", ... i.e. la chaîne "ab" suivie d'une ou plusieurs occurrences de la chaîne "cd" (ou séquence).
La barre verticale | permet de réaliser une opération logique OU
"ab|cd": chaîne qui contient "ab" ou "cd"
Un caractère spécial précédé d'un anti-slash (caractère d'échappement) est traité comme un caractère normal.
Des caractères spéciaux placés dans des crochets sont vus comme des caractères normaux.
Exemples:
"a\*b" : représente la chaîne "a*b"
[.\{?+] : n'importe quel caractère parmi "\", ".", "{", "?", "+"
>"[]+]": chaîne qui contient le caractère "]" ou le caractère "+"
"^ab+": chaîne commençant par "a" suivie de un ou plusieurs "b" ("ab", "abbbbbbb" ...)
"(un|le) (chat|chien)": chaîne qui contient "un chien" ou "le chien" ou "un chat" ou "le chat".
"(a|b)*": chaîne qui contient une suite de "a" ou de "b"
"^.{2}$": chaîne qui contient 2 caractères
".*" : Chaîne qui contient n'importe quelle suite de caractères.
| . | un caractère unique quelconque |
| * | répétition indéterminée de l'élément la précédant |
| ? | 0 à 1 fois l'élément précédant |
| [] | liste de caractères autorisés |
| {} | Nombre de reproductions de l'élément précédant |
| () | caractères de regroupement, l'expression régulière qu'elle contient devient un élément. |
| | | OU |
| - | Représentation d'un intervalle dans des crochets. |
| $ | Caractérise la fin d'une ligne. |
| ^ | Caractérise le début d'une ligne. |
| ^ | Négation dans une liste. |
| \ | Caractère d'échappement (anti-slash), protection du caractère qui suit. |
| [[:alpha:]] | est la même chose que [a-zA-Z] |
| [[:upper:]] | est la même chose que [A-Z] |
| [[:lower:]] | est la même chose que [a-z] |
| [[:digit:]] | est la même chose que [0-9] |
| [[:alnum:]] | est la même chose que [0-9a-zA-Z] |
| [[:space:]] | correspond à tout espace y compris la tabulation. |
Ces formes alternatives, comme [[:digit:]] sont préférables à la méthode directe [0-9]
Les exemples de cette partie sont basés sur le tutoriel grep de Donovan Rebbechi accessible à l'adresse suivante: http://pegasus.rutgers.edu/~elflord/unix/grep.html
grep est un outil Unix permettant de rechercher des lignes de textes dans un ou des fichiers. La recherche s'effectue sur la base d'une chaîne de caractères pouvant contenir une expression régulière.
grep est le premier outil étudié dans cet article qui nous permet d'appréhender les expressions régulières, de les mettre en pratique et d'aller un peu plus loin (je pense aux références arrières à la fin de ce chapitre).
Voir la page de man: man grep
grep [options] MOTIF [FICHIER...] grep [options] [-e MOTIF | -f FICHIER] [FICHIER...]
Le motif est une chaîne de caractères pouvant contenir une expression régulière.
Il est possible d'inclure le motif de recherche dans un fichier. Le motif écrit en expression régulière peut être long et s'il doit être réutilisé, alors il est profitable de le sauvegarder dans un fichier et d'appeler la commande grep ensuite avec l'option -f pour indiquer que le motif de recherche s'y trouve!
Recherche de la chaîne foo dans un fichier:
grep foo fichier
retourne toutes les lignes de fichier contenant la chaîne de caractères "foo".
Une autre façon d'utiliser grep est d'effectuer la recherche sur le résultat d'une autre commande. C'est à dire, appliquer la recherche sur l'entrée standard STDIN plutôt que dans un fichier.
Par exemple:
ls | grep bla
recherche tous les fichiers du répertoire courant dont le nom contient la chaîne "bla".
grep supporte les jokers dans la chaîne de recherche (joker = caractère spécial, utilisé entre autres dans les expressions régulières).
Le premier d'entre-eux est le caractère "." (point, dot). Le caractère "." signifie n'importe quel caractère.
Un exemple:
yuluth:~$ cat file
big
bad bug
bag
bigger
boogy
yuluth:~$ grep b.g file
big
bad bug
bag
bigger
A noter que la ligne correspondante à boogy ne correspond pas puisqu'il y a deux caractères entre le "b" et le "g".
Voir les expressions génériques.
A noter (même si déjà vu), l'association du point avec le caractère étoile ".*" veut dire n'importe quelle chaîne de caractères et correspond au joker "*" en ligne de commande du shell par exemple.
Illustration par l'exemple:
>cat file big bad bug bag bigger boogy
Exemple 1:
>grep "b.*g" file big bad bug bag bigger boogy
La ligne boogy est cette fois-ci prise en compte!
Exemple 2:
>grep "b.*g." file bigger boogy
Cas de répétition
>grep "ggg*" file bigger
En effet ici nous voulons trouver les lignes qui comprennent une chaîne commençant par "gg" et ayant ensuite zéro ou n occurrences de "g".
La ligne "bigger" correspond à cette description!
L'utilisation de base des jokers dans les motifs de recherche n'est qu'un début. Mais si nous voulions rechercher les lignes de texte dans un fichier qui contiendrait "Frederic Smith" ou bien "Fred Smith", ce qui veut dire que les lettres "eric" sont facultatives, comment allons nous le réaliser?
Nous avons besoin du caractère d'échappement "\", des caractères de regroupement "()" et du caractère "?" signifiant zéro ou une occurrence du motif précédant.
En effet, dans la recherche que nous voulons effectuer, la chaîne "eric" est facultative. Elle doit être présente ou non, ce qui pourrait être défini par l'opérateur "?" mais auparavant il nous faut grouper les caractères de la chaîne "eric" comme ceci:
(eric)?
Nous pourrions pensez alors que l'expression à utiliser serait:
grep "Fred(eric)? Smith" file
et nous commettons là une erreur, car nous voulons que les caractères suivants "()?" soient interprétés par grep, or ici ils sont d'abords interprétés par le shell. C'est ici que le caractère d'échappement intervient pour protéger ces caractères de l'interprétation du shell, la bonne expression est par conséquent:
grep "Fred\(eric\)\? Smith" file
Exemple:
grep "([^()]*)a" file
retourne toutes les lignes contenant une paire de parenthèses contenant tout sauf une autre paire de parenthèses et suivi du caractère "a". Les deux lignes suivantes correspondent à ce motif:
(hello)a (aksjdhaksj d ka)a
mais pas celle-ci:
x=(y+2(x+1))a
Un bon exemple est la recherche de lignes contenant un numéro de téléphone qui est composé de 10 chiffres (en France) qui peuvent être écrits avec un séparateur ".",sans espace ou avec des espaces (en regroupant les chiffres par paires) comme ceci:
02 40 43 56 17 ou bien 0240435617 ou bien encore 02.40.43.56.17
Pour déterminer les lignes d'un fichiers contenant des numéros de téléphones, nous écrivons:
grep "\([[:digit:]]\{2\}[ .]\?\)\{5\}" file
Supposons que nous voulons trouver toutes les lignes d'un texte contenant "hello" et rien d'autre que des espaces (au sens génériques du terme, i.e. espace et tabulation).
Exemple, nous avons le fichier file contenant ceci:
>cat file hello hello world hhello
Une simple recherche nous renvoit:
>grep hello file hello hello world hhello
Ce n'est pas ce que nous voulons! Seule la première ligne correspond à ce que nous cherchons.
En jetant un coup d'oeil au premier chapitre (les expressions régulières) nous avons les éléments pour répondre au problème:
> grep "^[[:space:]]*hello[[:space:]]*$" file hello
C'est tout!
Supposons que nous cherchions une chaîne qui contienne une sous-chaîne précise à plusieurs endroits. Un bon exemple, les balises de titre d'un fichier HTML.
Si nous voulions juste trouver les chaînes de caractères contenant un titre de niveau 1 comme <h1>chaîne quelconque</h1> ce serait facile à réaliser.
Exercice: Déterminer le motif de recherche pour ce cas.
Mais si nous voulons faire la même chose mais pour n'importe quel niveau de titre h1, h2, h3, ..., h6 en lieu et place de h1 uniquement, c'est un peu plus compliqué!
Une expression comme <h[1-6]>.*</h[1-6]> n'est pas suffisante car elle autorise des résultats comme <h1>Mon titre</h3>, ce qui n'est pas valide en HTML! Nous voulons qu'une balise HTML ouvrante corresponde à la balise fermante.
Nous avons besoin pour cela d'utiliser ce que l'on appelle une référence arrière!
L'expression \n ou n est un nombre, constitue cette référence arrière, qui correspond au contenu du n-ième regroupement précédent.
L'expression à utiliser dans grep pour répondre à notre problème est alors:
> grep "<h\([1-6]\).*</h\1>" file
Ici nous avons besoin de faire une référence au dernier regroupement ([1-6]). Nous voyons maintenant la deuxième utilité des parenthèses de regroupement. Dans le cas présent nous n'avions pas besoin de "regrouper un caractère!".
Et si nous voulions parfaire cette expression (une balise pouvant être écrite avec ou sans majuscule):
> grep "<[hH]\([1-6]\).*</[hH]\1>" file
En shell, les caractères de protection sont les guillemets, les apostrophes ou le caractère d'échappement. Nous avons vu des exemples avec le caractère d'échappement.
Tout d'abords il faut savoir que la meilleure protection de chaîne est réalisée avec les apostrophes.
Par exemple grep "!" file va souvent produire une erreur (le shell pense que "!" fait référence à l'historique des commandes) alors que grep '!' file n'en produira pas. Le guillement permet de caractériser une chaîne avec des espaces mais ne protège pas cette chaîne de l'interprétation des caractères spéciaux. Les apostrophes eux protège de l'interprétation par le shell de ce type de caractères.
Quand utiliser les apostrophes? Si vous avez besoins d'utiliser les variables du shell vous utiliserez les guillements, sinon les apostrophes!
Par exemple:
grep "$HOME" file
recherche dans le fichier file des lignes comprenant le chemin de votre répertoire personnel, tandis que
grep '$HOME' file
recherche juste la chaîne de caractères $HOME dans le fichier file
sed est un éditeur non-interactif de chaîne à l'origine écrit et conçu pour Unix, sed a été porté sur MSDOS, sur Windows, sur l'Os/2, et pour d'autres systèmes d'exploitation. sed fonctionne en ligne de commande, il change des blocs de texte à la volée. Il fonctionne un peu comme la fonction "chercher/remplacer" d'un éditeur de texte classique, mais ses possibilités sont beaucoup plus étendues. sed est extrêmement puissant, et il vous permet de faire des choses que vous ne pouvez pas faire dans aucune unité de traitement de texte standard.
Une partie de ce chapitre est une traduction de "An introduction to sed" sur le site Getting start with sed
Sed prend en arguments des "commandes sed" qui s'appuient principalement sur les expressions régulières et comme arguments supplémentaires les fichiers à traiter (parser).
Sed ne modifie pas les fichiers qui sont donnés en arguments, mais les traite par blocs de chaînes qui sont modifiées et renvoyés sur la sortie standard (l'écran par défaut).
Pour récupérer le résultat de la commande sed il faut effectuer une redirection de la sortie standard vers un fichier.
sed "une_ou_plusieurs_commandes" input.file > newfile.txt
Il est possible de regrouper des commandes dans un script et utiliser ce script pour traiter les commandes qu'il contient via sed comme suit:
sed -f sed.script input.file > newfile.txt
Voir la page de man: man sed
sed [OPTION]... {script-only-if-no-other-script} [input-file]...
La chose la plus frustrante lorque l'on apprend à utiliser sed est le passage des commandes sed à l'interprétation du shell. La manière la plus efficace d'empêcher l'interprétation des commandes sed par le shell est d'utiliser les apostrophes (quotes), comme ceci:
> sed 's/fubar/foobar/' file
Les apostrophes protègent pratiquement tout de l'interprétation du shell.
La commande sed qui est probablement la plus utilisée est la commande de substitution: s. Elle substitue une chose à une autre. La manière simple de l'écrire et généralement la plus utilisée est celle-ci:
> sed 's/coleur/couleur/g' file
Ici nous demandons à sed de rechercher toutes les occurrences "coleur" dans le texte file et de les remplacer par la chaîne "couleur" dans la sortie de sed en sortie standard (STDOUT, par défaut la console ou vous lancez la commande).
Le séparateur utilisé pour distinguer la commande de l'expression de recherche, de la chaîne de remplacement est le "/" (nous verrons que ce n'est pas obligatoire).
Le "g" est placé ici à la fin, modifie l'action de "s" pour donner le sens "global" à cette commande de substitution. En effet sans le modificateur de commande "g" la commande "s" s'arrêterait, dans sa recherche/substitution, à la première occurrence trouvée de chaque ligne. S'il y a 2 occurrences de "coleur" sur une ligne, seule la première serait substituée!
Pour sed (comme pour grep), il y a interprétation des expressions régulières, vous rencontrerez des problèmes si vous utilisez les caractères suivants comme caractères normaux sans les protéger (ici une protection vis à vis de sed):
.*[]^$\
Exemple, ne faites pas:
>sed 's/[J.S. Bach {$ for music}]/[Bach, J.S {$ for music}]/' filename
mais plutôt:
>sed 's/\[J\.S\. Bach {\$ for music}\]/[Bach, J.S {$ for music}]/' filename
A noter que cela ne s'applique pas à la chaîne de remplacement!
Que se passe t'il si nous voulions faire plusieurs remplacement en même temps? A première vue nous essayerons quelque chose comme ceci:
>sed 's/color/colour/g' 's/flavor/flavour/g' filename
Mais cela ne fonctionne pas. sed va chercher un fichier se nommant "g" dans le répertoire s/flavor/flavour/".
sed a une option "-e" qui indique que ce qui suit l'écriture de cette option est une commande de sed (fait partie du script sed). L'exemple ci-dessus s'écrirait alors de façon correcte comme suit:
>sed -e 's/color/colour/g' -e 's/flavor/flavour/g' filename
Vous devez alors indiquer cette option "-e" pour la première partie du script sed!
Vous pouvez utiliser l'option "-e" s'il y a une seule commande sed, mais comme nous l'avons déjà vu dans des exemples ce n'est pas obligatoire.
Les différentes commandes passées à sed sont traitées dans l'ordre où elles sont écrites, aussi si vous exécutez:
>sed -e 's/color/colour/g' -e 's/colour/color/g' filename
sed va remplacer "color" par "colour" et remplacer ensuite "colour" par "color". Le résultat ne sera pas l'équivalent du fichier original puisque toutes les occurrences qui pouvaient exister de "color" et de "colour" dans le fichier original vont devenir "color" dans le résultat de cette commande sed. A noter que, ici, c'est plutôt une manière inefficace d'arriver à ce résultat!
Quand est-il lorsque nous voulons remplacer des chaînes contenant le caractère "/"? Cela arrive souvent lorsque les chaînes recherchées pour remplacement sont des noms complets de fichiers. Vous pouvez protéger chacun des caractères "/" comme suit:
>sed 's/\/usr\/bin/\/bin/g' filename
Ceci est plutôt désagréable à écrire sur des chemins qui sont longs. Il y a une alternative au séparateur "/" pour la commande de substitution, sed interprète le caractère qui suit immédiatement la commande "s" comme caractère séparateur. Vous pouvez choisir alors ce que bon vous semble! Aussi l'exemple ci-dessus peut-être écrit plus agréablement comme ceci:
>sed 's#/usr/bin#/bin#g' filename
sed peut utiliser les expressions régulières. Nous allons voir quelques exemples d'utilisation.
Remplacement en début de ligne:
>sed 's/^Thu /Thursday/' filename
A noter ici que "g" n'est pas utilisé puisque nous avons qu'un début de ligne par ligne (belle lapalissade ;) ).
>sed 's/ $//' filename
supprime tout espace en fin de ligne.
Vous pouvez remplacer la fin d'une ligne comme ceci:
>sed 's/$/EOL/' filename
Cela ne va pas former une longue ligne, mais reproduire le contenu du fichier filename avec la chaîne "EOL" en chaque fin de ligne.
Pour spécifier une ligne vide:
>sed 's/^$/ligne non vide maintenant/' filename
Ceci remplace toutes les lignes vide par une ligne contenant la chaîne "ligne non vide maintenant".
Utilisation de '.' pour supprimer la date et l'heure d'indications de dates complètes dans un fichier de log. Par exemple des dates qui auraient cette forme Apr 12 10:58:11 1980:
>sed 's/Apr .. ..:..:.. 1980/Apr 1980/g' filename
Utilisation des crochets:
>sed 's/[Oo]pen[Ww]in/openwin/g' filename
avec indication d'un intervalle:
>sed 's/ [A-Z]\. / /g' filename
avec l'indicateur d'exclusion "^":
>sed 's/ [^A-DHM-Z]\. / /g' filename
Utilisation de "*":
>sed 's/ *$//' filename
supprime tous les espaces de fin de ligne, et
>sed 's/[ ]*$//' filename
supprime tous les espaces et tabulations de fin de ligne.
A noter qu'il est très important de savoir que "*" peut correspondre à 0 occurrence! Si vous avez besoin d'une correspondance sur un entier, par exemple:
>sed 's/ [0-9]* / integer /g' filename
va remplacer aussi les espaces par la chaîne "integer", ce qui n'était pas le but recherché, il vaut mieux écrire:
>sed 's/ [0-9][0-9]* / integer /g' filename
ce qui oblige à avoir au moins un entier dans les chaînes recherchées.
La combinaison ".*" veut dire n'importe quel nombre de n'importe quel caractère. Aussi,
>sed 's/col.*lapse/collapse/g' filename
va agir sur n'importe quelle ligne contenant "col" et ensuite "lapse" quel que soit le contenu entre ces deux chaînes. Le caractère spécial "*" est vorace, dans la commande ci-dessus il va chercher la plus grande chaîne possible, comme l'exemple ci-après:
Le script ci-dessus va remplacer la ligne suivante:
a b col d e f lapse h i j k lapse m n
par
a b collapse m n
au lieu de
a b collapse h i j k lapse m n
les "\(" et "\)" dans l'expression de recherche, permettent de sauvegarder ce qui se trouve entre eux. Nous avons déjà vu un exemple d'utilisation avec la commande grep. Le moyen de faire référence à ce qui a pu être sauvegardé entre des parenthèses est ici de la même manière "\n" où n est un entier qui fait référence à la n-ième sauvegarde précédente.
La première paire de "\(\)" fait une sauvegarde de son contenu qui peut être récupéré par "\1", la deuxième par "\2" et ainsi de suite...
Par exemple sur une liste de noms et prénoms contenue dans filename:
>sed 's/^\([A-Z][A-Za-z]*\), \([A-Z][A-Za-z]*\)/\2 \1/' filename
va remplacer 'Nom, Prénom" par "Prénom Nom". Noter que la virgule est placée délibérément en dehors des parenthèses, sinon, nous aurions obtenu des remplacements comme "prénom Nom,".
Il arrive que le besoin d'appliquer les commandes shell sur un fichier ne s'appliquent qu'à certaines lignes. Il est possible d'appliquer un critère de sélection des lignes qui seront traitées par les commandes de sed.
Le critère de sélection des lignes s'applique avant la commande "s".
Pour limiter l'action de sed qu'à un intervalle de lignes:
>sed '1,20s/foobar/fubar/g' filename
nous appliquons la recherche/remplacement qu'à partir de première ligne jusqu'à la vingtième ligne.
Si vous voulez appliquer des transformations sur des lignes qui contiennent une chaîne spécifique:
>sed '/^Aug/s/Mon /Monday /g' filename
Ou à des lignes qui ne contiennent pas telle chaîne:
avec sh ou ksh ou bash,
>sed '/^Aug/!s/Mon /Monday /g' filename
avec csh ou tcsh,
>sed '/^Aug/\!s/Mon /Monday /g' filename
Vous pouvez aussi appliquer la commande sur toutes les lignes entre une ligne commençant par une chaine et une autre ligne commençant par une autre chaîne (ces lignes inclusent):
>sed '/^Aug/,/^Oct/s/Mon /Monday /g' filename
Normalement sed lit une ligne, la traite et la renvoie à la sortie standard.
Le besoin peut se faire sentir de ne pas tout renvoyer à la sortie standard. L'option "-n" empêche l'impression après traitement de la ligne. Aussi,
>sed -n 's/fubar/foobar/g' filename
va être muet. Il faut utiliser le drapeau 'p' associé à la commande 's' pour obtenir l'impression de ce qui a été traité:
>sed -n 's/fubar/foobar/gp' filename
Si votre script sed commence à être long vous pouvez les commandes qui le composent dans un fichier come ceci:
> cat sample.sed # This file is named "sample.sed" # comments can only appear in a block at the beginning s/color/colour/g s/flavor/flavour/g s/theater/theatre/g
Il suffit ensuite de l'appeler avec l'option "-f" comme ceci:
>sed -f sample.sed filename
On peut rendre aussi un script sed exécutables:
> cat sample2.sed #!/usr/bin/sed -f # This file is named "sample2.sed" s/color/colour/g s/flavor/flavour/g s/theater/theatre/g
avec les bonnes permissions,
>chmod u+x sample2.sed
et ensuite on l'appelle comme n'importe quel script:
>./sample2.sed filename
Cette partie est inspirée d'un document écrit à l'origine par Andrew M. Ross en version anglaise.
Il y a trois versions répandue pour awk: awk, nawk, et gawk. La dernière est la plus compatible avec l'original.
La page d'info de gawk vous donnera plus d'informations et de l'information plus conséquente.
>info gawk
Références:
Une fois que vous commencerez à avoir une connaissance minimale sur l'utilisation de awk, la page de man de awk deviendra vraiment utile:
> man gawk
Awk dans les news (newsgroup): comp.lang.awk
Awk est une commande qui prend en entrée le contenu d'un fichier ou l'entrée standard (le clavier par défaut, ou une redirection de STDIN) et en sortie la sortie standard (STDOUT).
En général, on redirige la sortie de cette commande vers un fichier. Dans les exemples suivants nous ne le ferons pas.
Vous ne pouvez pas utiliser awk sur des fichiers non-textes comme des exécutables ou des fichiers FrameMaker.
Comme pour grep et sed il faut penser à protéger les commandes awk de l'interprétation du shell, comme ceci:
>awk '{print $0}' filename
Quelques exemples:
La manière la plus rapide d'appréhender le fonctionnement de base de awk est encore de s'appuyer sur des exemples.
Le premier exemple que vous avez eu dans ce chapitre se comporte exactement comme la commande cat, il se contente de reproduire vers la sortie standard le contenu du fichier.
Voici d'autres exemples:
>awk '{print $2,$1}' filename
Va afficher d'abords le second champ puis le premier pour chaque ligne. Tous les autres champs sont ignorés.
>awk '{print $1,$2,sin($3/$2)}' filename
affiche le premier champ puis le second et enfin le résultat du calcul du sinus du rapport entre le troisième champ et le deuxième (il vaudrait mieux que les deuxième et troisième champ de chaque ligne soient des nombre ;) ).
A travers ce dernier exemple, nous voyons que awk intègre des fonctions mathématiques. Cela n'est que le début d'un aperçu de sa puissance! Sur la page de man vous avez l'ensemble des applications mathématiques que awk renferme.
Si vous voulez définir une condition de traitement de awk par ligne? Par exemple, nous voulons effectuer un traitement sur la ligne que si le premier champ est supérieur au deuxième (sens numérique), le programme suivant le réalise:
>awk '$1 > $2 {print $1,$2,$1-$2}' filename
La partie en dehors des accolades est appelé "pattern" (configuration) ou condition. La partie dans les accolades représente l'action.
Les opérateurs de comparaisons qui sont utilisables ici sont:
| == | égalité |
| != | différence |
| < | inférieur |
| > | supérieur |
| <= | inférieur ou égal |
| >= | supérieur ou égal |
Si aucune condition n'est indiquée alors le traitement (décrit dans la zone d'action) est appliqué à toutes les lignes du fichier. C'est ce que nous avons vu dans les exemples précédents.
Si aucune action n'est décrite, alors la ligne entière est envoyée vers la sortie standard (imprimée).
Si la commande "print" est utilisée seule, la ligne entière est imprimée. Par conséquent les lignes suivantes sont équivalentes:
awk '$1 > $2' filename
awk '$1 > $2{print}' filename
awk '$1 > $2{print $0}' filename
Les champs d'une ligne peuvent aussi être traités comme des chaînes de caractères plutôt que des nombres. Pour comparer un champ avec une chaîne de caractères utilisez la méthode suivante:
>awk '$1=="foo"{print $2}' filename
Que faire si vous voulez traiter les lignes où une certaine chaîne de caractère doit être trouvée? Il suffit de placer une expression régulière dans la condition (zone pattern), comme suit:
>awk '/foo.*bar/{print $1,$3}' filename
ceci va afficher toutes les champs 1 et 3 des lignes qui incluent la chaîne "foo" et ensuite plus loin dans la même ligne la chaîne "bar".
Si vous voulez obtenir uniquement les lignes où "foo" est contenu dans le deuxième champ il faut utiliser l'opérateur "~" ("contien"):
>awk '$2~/foo/{print $3,$1}' filename
Si à l'inverse vous voulez traiter les lignes où "foo" n'est pas contenu dans le deuxième champ, on ajoute à l'opérateur précédent la négation "!~"
>awk '$2!~/foo/{print $3,$1}' filename
Cet opérateur "!~" peut être lu comme "ne contient pas".
Il est possible d'écrire des conditions évoluées (dans le pattern) avec des opérateurs booléens, ce sont:
| ! | non, négation |
| && | et |
| || | ou |
| () | groupement |
Il y a trois formes particulières pour exprimer la condition (le pattern) qui ne correspondent pas à ce que nous avons vu jusqu'ici. La première est la paire d'expression régulière commencement-fin. Par exemple pour afficher toutes les lignes entre les lignes qui contiennent "foo" et "bar" (ces deux lignes incluses), vous utiliserez:
>awk '/foo/,/bar/' filename
Les deux autres formes spéciales sont similaires; La forme "BEGIN" et la forme "END".
Toute action associée à la forme BEGIN va être effectuée avant le traitement de tout enregistrement (de toute ligne).
Toute action associée à la forme END va être effectuée après le traitement de tous les enregistrements.
Mais comment placer ces paires de condition/action dans un programme awk? Il y a plusieurs possibilités.
1. Tout mettre ensemble comme ceci:
>awk 'BEGIN{print"fee"} $1=="foo"{print"fi"} END{print"fo fum"}' filename
2. Une autre possibilité est de mettre le programme awk dans un fichier, comme ceci:
> cat giant.awk
BEGIN{print"fee"}
$1=="foo"{print"fi"}
END{print"fo fum"}
Et pour le faire fonctionner, nous utilisons l'option "-f" (comme sed):
>awk -f giant.awk filename
3. Créer un script awk autonome:
> cat giant2.awk
#!/usr/bin/awk -f
BEGIN{print"fee"}
$1=="foo"{print"fi"}
END{print"fo fum"}
En plaçant les bons droits d'exécution:
>chmod u+x giant2.awk
il suffit alors d'appeler le script comme ceci:
>./giant2.awk filename
awk a des variables qui peuvent être à la fois des nombres ou des chaînes. Par exemple, le code qui suit va afficher chaque ligne précédée de la somme des cinquièmes champ des lignes précédemment traitées:
>awk '{print x+=$5,$0 }' filename
Cela peut-être utile à appliquer à la sortie de la commande "ls -l", puisque le cinquième champ correspond à la taille de chaque fichier listé.
Les variables sous awk sont initialisées à 0 ou à la chaîne vide la première fois qu'elles sont utilisées. Cela dépend de la façon de les utiliser (en entier où en chaîne) bien sûr.
Les variables sont utiles pour garder des valeurs intermédiaires. L'exemple suivant introduit de plus la notion de point-virgule comme séparateur de commandes awk:
>awk '{d=($2-($1-4));s=($2+$1);print d/sqrt(s),d*d/s }' filename
La dernière commande (print) ne se termine pas par un point-virgule mais cela ne coûte rien de l'ajouter ;).
Les variables entières peuvent être utilisées pour se référer aux champs. Si un champ contient de l'information sur le champ qui est réellement important, ce script va afficher seulement ce champ là.
>awk '{imp=$1; print $imp }' filename
A méditer:
> ls -l | awk '{imp=$2; print $imp }'
Le variable spéciale NF vous indique combien de champs il y a dans un enregistrement (une ligne). Ce script affiche le premier et le dernier champ de chaque ligne, quelque soit le nombre de champ par enregistrement:
>awk '{print $1,$NF }' filename
La variable spéciale NR indique quel est le numéro d'enregistrement en cours. Il est incrémenté à chaque ligne traitée. Cela nous donne une façon simple de rajouter le numéro de ligne dans un fichier:
>awk '{print NR,$0 }' filename
La variable spéciale FS (Field Separator ou séparateur de champ, l'espace par défaut sous awk) détermine comment awk va décomposer chaque enregistrement en champs. Cette variable peut être positionné en ligne de commande. Par exemple, le fichier /etc/passwd a son séparateur de champ qui est ":". Si on veut obtenir de ce fichier, uniquement l'identifiant et l'uid des utilisateurs du système:
>awk -F: '{print $1,$3 }' /etc/passwd
Les différents champs sont aussi des variables et on peut aussi les affecter. Par exemple, si vous voulez supprimer le dixième champ de chaque ligne, vous pourriez le faire comme ceci:
>awk '{$10=""; print }' filename
awk est très similaire au langage C. Les constructions "for", "while", "do-while", et "if" existent toutes. Par exemple ce script (qui ne sert pas à grand chose autre qu'à titre d'exemple) va afficher tous les champs de chaque ligne sur sa propre ligne:
>awk '{for(i=1;i<=NF;i++) print $i }' filename
Il est possible d'utiliser un format de sortie d'affichage de la même façon qu'en langage C avec la commande printf comme suit:
>awk '{printf("%s %03d %02d %.15g\n",$1,$2,$3,$3/$2); }' filename
A noter que avec printf vous êtes obliger d'indiquer le caractère nouvelle ligne "\n".
Vous pouvez utilisez "printf" pour afficher des choses sans retour à la ligne, ce qui peut être pratique dans une boucle. Ce script affiche chaque enregistrement avec ses champs dans l'ordre inverse.
>awk '{for(i=NF;i > 0;i--) printf("%s",$i); printf("\n"); }' filename
awk gère les tableaux, mais ils ne peuvent être indexés que par des chaînes de caractères. Ceci peut être très utile dans certains cas mais aussi très ennuyeux dans d'autres.
Par exemple, il est aisé de compter la fréquence de mots dans un document:
>awk '{for(i=1;i <=NF;i++) freq[$i]++ }' filename
Ce tableau va garder une valeur entière pour chaque mot qui se trouve dans le fichier. Malheureusement il va traiter "foo", "Foo" comme des mots différents (la casse est prise en compte).
Comment allons nous afficher les différents mots et leur fréquence d'utilisation? awk a une construction spéciale de la boucle "for" pour boucler sur les valeurs d'un tableau. Ce script est plus long que ceux que nous avons vu jusqu'ici, aussi il est placé dans un fichier exécutable:
#!/usr/bin/awk -f
{for(i=1;i <=NF;i++) freq[$i]++ }
END{for(word in freq) print word, freq[word] }
Le résultat de ce script nous donne les mots et leur fréquence dans un fichier texte sous forme non triée. La sortie de ce script peut être envoyée à la commande sort via un pipe.
Vous pouvez utilisez awk pour créer du texte aussi bien que pour traiter un fichier texte. C'est une façon pratique pour générer rapidement, par exemple, des tables de valeurs de fonctions sans passer par la compilation d'un programme C. Par exemple, obtenir de façon interactive les valeurs de sin(x)/x qui se rapprochent de 1 quand x se rapproche de 0:
>awk '{x=1.0/NR; print x,sin(x)/x;}'
va afficher une nouvelle valeur à chaque fois qu'il lit une nouvelle ligne. Comme nous n'avons pas indiqué de fichiers à traiter, il traite les entrées clavier du clavier (STDIN). Aussi vous pouvez appuyer sur la touche entrée jusqu'à ce que vous ayez toutes les valeurs qui vous intéressent.
D'une autre manière, si vous avez besoin directement d'un certain nombre de valeurs, vous pouvez faire:
>awk 'BEGIN{for(i=1;i <=30;i++){x=1.0/i;print x,sin(x)/x;}}' /dev/null
où 30 est le nombre de valeurs à afficher.
| {} | délimite la zone d'action et pour grouper les commandes dans cette zone. |
| $ | détermine un champ. $1 est le premier champ, $0 est l'enregistrement en entier. |
| ~ | L'opérateur "contient". "foobar"~"foo" est vrai. Ne concerne que les chaînes de caractères. |
| !~ | l'opérateur pour "ne contient pas". Ne concerne que les chaînes de caractères. |
| == | l'opérateur d'égalité. Concerne les nombres et les chaînes. |
| < > <= >= != | Opérateurs d'inégalité. Concerne les nombres et les chaînes. |
| # | Le caractère de commentaire. |
| , | séparateur pour les commandes print et printf. |
| ; | Séparateur de commandes awk. |
| // | utilisé pour les expressions régulières |
| && | Opération booléenne "et" |
| || | Opération booléenne "ou" |
| ! | Négation booléenne |
| () | utilisé pour regrouper des expressions booléennes, pour passer des arguments aux fonctions, et pour les conditions de contrôle "for","while", etc. |