Les Expressions Régulières
Résumé:
Les expressions régulières sont utilisées dans les
recherches et modifications de texte sophistiquées. On les rencontre
dans de nombreux éditeurs, dans les programmes d'extraction et dans
certains langages.
Introduction
Les expressions régulières se rencontrent dans de nombreux
éditeurs sophistiqués comme vi et emacs, dans les programmes
grep/egrep, et dans les langages tels awk, perl et sed.
Les expressions régulières sont utilisées pour
les recherches et les modifications de texte contextuelles avancées.
L'expression régulière est une description formelle d'une
forme à comparer à une chaîne de caractères.
La première fois que j'ai vu quelqu'un utiliser les expressions
régulières, j'ai été fasciné. Des éditions
de texte et des recherches qui auraient pris normalement plusieurs heures
pouvaient être exécutées en quelques secondes. Cependant,
je ne comprenais pas un mot lorsque je voyais les expressions à
l'écran. Elles n'étaient qu'une étrange combinaison
de points, barres, étoiles et autres caractères. J'étais
néanmoins déterminé à percer le mystère,
et j'ai rapidement découvert que les expressions régulières
sont très faciles à utiliser. Elles suivent quelques simples
règles de syntaxe.
Bien que les expressions régulières soient très
répandues dans le monde Unix, il n'existe pas de "langage standard
pour les expressions régulières". Il existe plutôt
quelques dialectes différents. Par exemple, il existe deux programmes
de recherche de chaînes dans les fichiers: grep et egrep. Les deux
utilisent les expressions réguilières, mais avec des possibilités
légèrement différentes. Perl dispose probablement
du plus grand ensemble d'expressions régulières. Heureusement,
les principes sont toujours les mêmes. Une fois comprise l'idée
de base, il est aisé d'apprendre les détails des différents
dialectes.
Cet article vous présentera les principes de base, et vous pourrez
vous référer au manuel des différents programmes pour
connaître en détail ses règles et possibilités
particulières.
Un exemple simple
Supposons que vous ayez l'agenda d'une société sous la forme
suivante:
Phone Name ID
...
...
3412 Bob 123
3834 Jonny 333
1248 Kate 634
1423 Tony 567
2567 Peter 435
3567 Alice 535
1548 Kerry 534
...
La société comporte 500 personnes. Les données sont
conservées dans un simple fichier texte en ASCII. Supposons que
le premier chiffre du numéro de téléphone corresponde
au bâtiment où le collaborateur travaille. Comment savoir
qui travaille dans le bâtiment 1?
Les expressions régulières permettent de trouver rapidement
la réponse:
grep '^1' phonelist.txt
or
egrep '^1' phonelist.txt
or
perl -ne 'print if (/^1/)' phonelist.txt
En d'autres termes, cela signifie rechercher toutes les lignes qui commencent
par un "1". le symbôle "^" peut être traduit par "début
de ligne". Il force l'expression entière à correspondre seulement
si "1" est le premier caractère de la ligne.
La syntaxe
Formes à caractère unique
La pierre angulaire d'une expression régulière est la forme
à caractère unique. Elle ne correspond qu'à ce caractère.
Dans l'exemple précédent, le "1" est une forme à caractère
unique. Il ne correspond qu'à "1" dans le texte.
Un autre exemple de formes à caractère unique est:
egrep 'Kerry' phonelist.txt
Cette forme est composée de plusieurs formes à caractère
unique (les lettres K,e,r,r,y).
Les caractères peuvent être regroupés dans un ensemble.
L'ensemble est constitué d'une liste de caractères placée
entre crochets. L'ensemble lui-même est une forme à caractère
unique, car il coïncide à un unique caractère du texte.
Il suffit qu'un des caractères de l'ensemble soit présent
dans le texte pour que la correspondance soit vérifiée. Par
exemple:
[abc] est une forme à caractère unique
qui correspond
indifféremment
à l'un des caractères a, b ou c.
[ab0-9] est une forme à caractère unique qui correspond
aux caractères
a ou b ou un caractère entre 0 et 9.
[a-zA-Z0-9\-] cet ensemble correspond à un caractère
unique qui
est soit une lettre minuscule ou majuscule,
soit un chiffre, ou encore le signe moins.
Essayons:
egrep '^1[348]' phonelist.txt
Cette recherche extrait les lignes du fichier qui commencent par 13, 14
ou 18.
Nous venons de voir que pour la plupart, les caractères ASCII
ne correspondent qu'à eux-mêmes. Néanmois, certains
ont une signification spéciale. Les crochets définissent
un ensemble, le signe moins "-" détermine une plage. Pour annuler
le caractère spécial d'un symbôle, il suffit de le
faire précéder d'une barre inversée "\". C'est ce
que nous avions dans l'exemple précédent. Dans certains dialectes,
certaines séquences de contrôle commencent par une barre inversée,
qu'il faut alors supprimer pour retrouver la signification normale.
Le point est un caractère spécial important. Il correspond
à n'importe quel caractère sauf le saut de ligne. Par exemple:
grep '^.2' phonelist.txt
or
egrep '^.2' phonelist.txt
Ces commandes donnent toutes les lignes dont le deuxième caractère
est "2", le premier caractère étant quelconque.
Les ensembles peuvent être inversés (négation) en
indiquant "[^" à la place de "[" comme marque de début d'ensemble.
Ainsi, le symbôle "^" ne signifie plus début de ligne, mais
la combinaison "[^" indique ensemble inversé.
[0-9] est une forme à caractère unique
qui correspond
aux chiffres entre
zéro et neuf.
[^0-9] correspond à tout caractère ne représentant
pas un chiffre.
[^abc] correspond à tout caractère différent
de "a", de "b", et de "c".
. le point correspond à
n'importe quel caractère sauf le saut de ligne,
il est donc équivalent
à "[^\n]" où "\n" est le saut de ligne.
Pour trouver toutes les lignes ne commencant pas par "1", il faudrait
écrire:
grep '^[^1]' phonelist.txt
ou
egrep '^[^1]' phonelist.txt
Ancrages
Nous avons vu que le symbôle "^" correspond à un début
de ligne. Les ancrages sont des expressions régulières spécialement
prévues pour correspondre à une position dans le texte et
non à un caractère du texte.
^ correspond à un début de ligne
$ correspond à une fin de ligne
Pour retrouver les personnes ayant le matricule 567 dans votre agenda,
vous pouvez utiliser:
egrep '567$' phonelist.txt
Cette commande donnera les lignes se terminant par 567.
Répétitions
Il est possible de spécifier combien de fois une forme à
caractère unique doit se répéter.
Description |
grep |
egrep |
perl |
vi |
vim |
vile |
elvis |
emacs |
Zéro ou plusieurs fois |
* |
* |
* |
* |
* |
* |
* |
* |
Une ou plusieurs fois |
\{1,\} |
+ |
+ |
|
\+ |
\+ |
\+ |
+ |
Zéro ou une fois |
\? |
? |
? |
|
\= |
\? |
\= |
? |
Entre n et m fois |
\{n,m\} |
|
{n,m} |
|
|
|
\{n,m\} |
\{n,m\} |
Remarque: les différentes variations de vi ont leur option magique
positionnée pour fonctionner comme indiqué.
Un exemple sur l'agenda téléphonique:
....
1248 Kate 634
....
1548 Kerry 534
....
Pour retrouver une ligne qui commence par "1", puis au moins un chiffre,
puis au moins un espace, enfin un nom commencant par "K", on pourrait écrire:
grep '^1[0-9]\{1,\} \{1,\}K' phonelist.txt
ou utiliser "*" et répéter l'ensemble "[0-9]" et l'espace:
grep '^1[0-9][0-9]* *K' phonelist.txt
ou
egrep '^1[0-9]+ +K' phonelist.txt
ou
perl -ne 'print if (/^1[0-9]+ +K/)' phonelist.txt
Notez que la répétition intervient sur la forme à
caractère unique précédente. Ainsi, "23*4" ne signifie
PAS "2 puis 3 puis n'importe quoi puis 4" (cela s'écrirait "23.*4"),
mais signifie "une fois 2, peut-être plusieurs fois 3, puis une fois
4".
Il faut aussi remarquer que les répétitions sont gourmandes.
C'est à dire que la première répétition dans
la forme s'étend le plus possible vers la droite du texte.
Ainsi l'expression "^1.*4" renverrait la ligne suivante en entier:
1548 Kerry 534
depuis le premier caractère jusqu'au dernier. Ca n'a pas d'incidence
pour les outils comme grep, mais la différence est importante pour
les éditions et les substitutions.
Mémorisation avec les parenthèses
La contruction de mémorisation avec les parenthèses ne change
pas la correspondance d'une expression mais permet de mémoriser
le texte renvoyé, afin d'être utilisé plus loin dans
l'expression.
Le texte mémorisé est disponible à travers des
variables. La première parenthèse de l'expression est la
variable une, la seconde parenthèse la variable deux, etc.
Nom du programme |
Syntaxe des parenthèses |
Syntaxe des variables |
grep |
\(\) |
\1 |
egrep |
() |
\1 |
perl |
() |
\1 or ${1} |
vi,vim,vile,elvis |
\(\) |
\1 |
emacs |
\(\) |
\1 |
Par exemple, l'expression "[a-z][a-z]" renverrait deux lettres minuscules.
On peut réutiliser les deux caractères renvoyés afin
de rechercher des formes comme 'otto':
egrep '([a-z])([a-z])\2\1'
La variable 1 contient le caractère "o", la variable 2 le caractère
"t". Cette expression retrouverait aussi le texte "anna", mais pas "toto".
Cette construction de mémorisation avec les parenthèses
n'est pas souvent utilisée pour des recherches de texte comme otto
ou anna, mais plutôt lors des éditions ou pour des substitutions.
Edition de texte avec les expressions régulières
Pour l'édition, on peut utiliser un éditeur comme vi ou emacs,
ou utiliser des outils ou langages comme perl.
Dans emacs, la commande à exécuter est query-replace-regexp
(demande-remplacement-expressions régulières M-x), qui peut
aussi être affectée à une touche de fonction. Vous
pouvez aussi utiliser la commande replace-regexp (remplacement-expressions
régulières). La première commande est interactive,
la seconde ne l'est pas.
Dans vi, la commande de substitution est "%s/ / /gc". Le symbôle
"%" fait référence à l'étendue d'action de
la commande (fichier complet), et peut être remplacé par n'importe
quelle étendue appropriée. Par exemple, dans vim utilisez
shift-v, marquez une étendue puis exécutez la substitution
sur cette étendue. Je ne m'étendrai pas plus sur vim, qui
mériterait un article à lui seul. Dans la commande de substitution,
"gc" fait référence à la version interactive. La version
sans confirmation est "s/ / /g".
Interactif signifie ici demande de confirmation avant chaque remplacement.
Avec perl vous pouvez utiliser:
perl -pe 's/ / /g'
Essayons quelques exemples. Supposons que le plan de numérotation
soit modifié, et qu'on doive insérer un "2" après
le deuxième caractère pour tous les numéros commencant
par "1".
Par exemple, le numéro 1423 devient 14223.
Le répertoire téléphonique avant modification:
Phone Name ID
...
3412 Bob 123
3834 Jonny 333
1248 Kate 634
1423 Tony 567
2567 Peter 435
3567 Alice 535
1548 Kerry 534
...
En exécutant l'une des commandes suivantes:
vi: s/^\(1.\)/\12/g
emacs: ^\(1.\) replaced by \12
perl: perl -pe 's/^(1.)/${1}2/g' phonelist.txt
le répertoire devient:
Phone Name ID
...
3412 Bob 123
3834 Jonny 333
12248 Kate 634
14223 Tony 567
2567 Peter 435
3567 Alice 535
15248 Kerry 534
...
Perl n'est pas limité aux variables mémoires " \1" à
"\9". Ainsi, "\12" ferait référence à la douzième
variable mémoire, d'où la syntaxe "${1}" pour référencer
la première variable mémoire.
Vous remarquerez que l'alignement dans la liste est dorénavant
perturbé. Comment remédier à ce problème? On
peut tester si le caractère en cinquième position est un
espace, et sinon ajouter un espace:
vi: s/^\(....\) /\1 /g
emacs: '^\(....\) ' replaced by '\1 '
perl: perl -pe 's/^(....) /${1} /g' phonelist.txt
Maintenant le répertoire a retrouvé un aspect plus agréable:
Phone Name ID
...
3412 Bob 123
3834 Jonny 333
12248 Kate 634
14223 Tony 567
2567 Peter 435
3567 Alice 535
15248 Kerry 534
...
Un collègue a manuellement édité le répertoire
en introduisant accidentellement des espaces au début de certaines
lignes. Comment les enlever?
Phone Name ID
...
3412 Bob 123
3834 Jonny 333
12248 Kate 634
14223 Tony 567
2567 Peter 435
3567 Alice 535
15248 Kerry 534
...
Ces commandes devraient apporter la solution:
vi: s/^ *// (il y a 2 espaces car nous n'avons pas le +)
emacs: '^ +' remplacé par la chaîne vide
perl: perl -pe 's/^ +//' phonelist.txt
Autre exemple pour les développeurs. Vous écrivez un programme
qui comporte les deux variables "temp" et "temporaire". Maintenant vous
souhaiteriez remplacer le nom de variable "temp" par "compteur".
Si la chaîne "temp" est simplement remplacée par "compteur",
alors l'autre variable prendra le nom "compteuroraire", ce qui n'est pas
ce que vous souhaitez!
Les expressions régulières peuvent le faire. Remplacez
simplement "temp([^o])" par "compteur\1". C'est à dire que l'on
recherche maintenant la chaîne "temp" puis n'importe quel caractère
différent de "o". (Une solution alternative aurait été
d'utiliser les limites, mais nous n'avons pas discuté ce type d'ancrage.)
J'espère que cet article aura suscité votre intérêt
pour les expressions régulières. Je vous conseille de jeter
un oeil aux pages d'aide et à la documentation de votre éditeur
favori pour plus de détail.
Bien sûr cet article n'est qu'une brève introduction, il
existe d'autres caractères spéciaux, comme par exemple l'altération
(sorte de OU logique), et aussi les limites de mots évoquées
précédemment.
Bonne édition, amusez-vous bien!
Traduit par Jean-Denis Girard. |