Voici le quatrième article de notre série introductive sur la programmation
Perl. Cette fois nous abordons un sujet très riche en développements :
les expressions régulières. Perl en tire une partie de sa grande puissance
pour l'analyse et le traitement des données textuelles.
La lecture de cet article peut aussi intéresser toute personne utilisant
grep
, sed
, Python, PHP
et même Java.
Atout important de Perl par rapport à d'autres langages, les expressions régulières permettent de manipuler le texte de façon très puissante et très concise. L'acquisition de leur maîtrise peut s'avérer difficile au début, mais en vaut très largement la chandelle, aussi bien pour programmer en Perl que pour utiliser les outils classiques du shell ou les autres langages précédemment cités.
Au niveau vocabulaire, on utilise en anglais le terme regular expression (souvent abrégé en regexp voire regex), ce qui a donné en français une traduction correcte "expressions rationnelles" et une traduction mot à mot "expressions régulières". La seconde est entrée dans les moeurs et sera donc utilisée ici.
On retrouve les expressions régulières dans certaines fonctions Perl
que vous connaissez déjà, comme par exemple split
ou grep
; mais elles existent aussi par
le biais d'opérateurs spécifiques.
Il existe deux types principaux de fonctionnalités dans les expressions régulières : la correspondance (pattern matching en anglais : pattern=motif, matching=correspondance) et la substitution.
La correspondance est le fait de tester (vérifier) si une chaîne de
caractères comporte un certain motif. Par exemple, on pourrait se poser
les questions suivantes et y répondre par un match :
la variable $v
commence-t-elle par un chiffre ?
Comporte-t-elle au moins deux lettres majuscules ? Contient-elle une
sous-chaîne répétée deux fois d'au moins 5 caractères ? Etc.
Sa syntaxe est la suivante :
m/
motif/
Le m
indique que nous voulons faire un
match, les slashes (/
) servent
à délimiter le motif recherché (on verra plus loin comment utiliser
d'autres séparateurs).
Cette fonctionnalité nous permettra aussi d'extraire des sous-chaînes d'une
variable donnée sans la modifier. Par exemple, si je veux récupérer le
premier nombre que comporte $v
, j'utiliserai
aussi la correspondance.
La substitution permet de faire subir des transformations à la valeur
d'une variable. Par exemple : remplacer dans la variable
$v
toutes les sous-chaînes toto
par titi
. Supprimer de $v
tous les mots entre guillemets. Etc.
Sa syntaxe est la suivante :
s/
motif1/
motif2/
Le s
indique que nous voulons faire une
substitution, les slashes (/
) servent
à délimiter le motif recherché ainsi que la chaîne de remplacement.
Pour "lier" une variable à une telle expression, il faut utiliser
l'opérateur =~
(dit bind en anglais).
$v =~ m/sentier/
vérifie si la variable
$v
comporte le mot sentier
.
On dit alors que la variable est "liée" à l'expression régulière.
Cette expression vaut vrai ou faux ; nous l'utiliserons donc très
souvent dans une structure de contrôle de type
if
:
if( $v =~ m/sentier/ ) { instructions }
Dans les cas où le test est vrai, c'est-à-dire si la variable contient
le motif (ici si elle contient sentier
), les
instructions seront exécutées.
Par cette opération de bind, nous venons de lier une expression
régulière à une variable. Par défaut, une telle expression s'applique
à la variable $_
(comme beaucoup de fonctions Perl).
De la même façon,
$v =~ s/voiture/pieds/;
remplace la première occurrence de voiture
dans la variable $v
par pieds
(on verra plus loin comment remplacer toutes les occurrences).
Le reste de $v
n'est pas modifié.
Le point-virgule indique la fin de l'instruction.
Pour la correspondance, il existe aussi l'opérateur
!~
qui équivaut à =~
suivi d'une
négation de l'expression.
if( $w !~ m/pieds/ ) { ... }
est plus concis et est équivalent à
if( ! ( $w =~ m/pieds/ ) ) { ... }
Les instructions sont exécutées si $w
ne contient
pas la chaîne pieds
.
Dans cette partie nous allons voir quels sont les motifs utilisables dans les expressions régulières.
Dans le cas général, un caractère vaut pour lui même ; comprenez que
lorsque l'on utilise l'expression régulière m/a/
on vérifie si la variable (ici non citée) contient le caractère
a
. Cela semble évident, mais il est bon de le dire.
En effet, pour certains caractères spéciaux, cela n'est pas le cas.
Ces caractères ont un rôle particulier dans les expressions régulières
(nous allons voir cela dans la suite). Si vous avez besoin de rechercher
ces caractères, il faut donc les déspécifier au moyen d'un anti-slash
(\
). Voici la liste de ces caractères :
\ | ( ) [ ] { } ^ $ * + ? .
Il faut ajouter à cette liste le caractère choisi comme séparateur.
Pour le moment, seul le slash (/
) est utilisé dans
cet article, mais nous verrons plus tard qu'il est possible d'en changer.
Par exemple $x =~ m/to\.to/
est vrai si la variable
$x
comporte les caractères t o . t o
contigus.
Les caractères spéciaux habituels peuvent être utilisés ; en voici quelques exemples :
Motif | Caractère |
\n |
saut de ligne |
\r |
retour chariot |
\t |
tabulation |
\f |
saut de page |
\e |
échappement |
Le caractère .
(point) correspond à un caractère
quel qu'il soit (sauf \n
(ce comportement peut être changé :
nous verrons cela plus loin)). Cela signifie qu'à l'emplacement
de ce point dans le motif pourra (devra) correspondre un caractère
quelconque dans la variable. Par exemple, le motif m/t.t./
reconnaîtra toute variable comportant une lettre t
suivie d'un caractère quelconque, puis une autre lettre t
,
puis un autre caractère quelconque ; par exemple toute variable comportant
une des chaînes suivantes
correspondra au motif : tata
,
t%tK
, tot9
...
Vous comprenez pourquoi il faut déspécifier le caractère point avec un anti-slash si vous voulez chercher un point littéral : sans cela un point matche avec n'importe quel caractère.
Le motif [
caractères]
matche un caractère parmi ceux présents entre crochets. Par exemple
[qwerty]
peut reconnaître une de ces six lettres.
Le motif m/t[oa]t[ie]/
reconnaîtra toute variable
comportant une des quatre chaînes suivantes : toti
,
tati
, tote
ou tate
.
On comprendra aisément que si un caractère est présent plusieurs fois
dans cette liste, cela a le même effet que s'il était présent une seule
fois : [aeiouyie]
est équivalent à
[aeiouy]
.
Il est possible de définir des intervalles de caractères dans ces ensembles.
Par exemple a-z
équivaut aux 26 lettres minuscules
de l'alphabet. Par exemple [2a-zR]
entrera en
correspondance avec toute lettre minuscule ou bien avec le 2 ou bien
avec le R majuscule. On peut aussi utiliser les ensembles
A-Z
ou 0-9
; par extension
tout intervalle est envisageable, par exemple R-Z
ou toute autre combinaison tant que le numéro ASCII du premier caractère
est inférieur à celui du second. Autre exemple, le motif
[ -~]
correspond à un caractère ASCII imprimable
et de numéro inférieur à 127.
Un intervalle peut prendre place au milieu d'un motif quelconque :
m/tot[a-zA0-9]V/
matche
totaV
, totbV
...
totzV
,
totAV
,
tot0V
... tot9V
.
Si le caractère tiret (-
) doit être présent dans
l'ensemble, il faut le mettre en première ou en dernière position afin de lever toute
ambiguïté possible avec un intervalle. Par exemple
[a-z4-]
matche soit une minuscule, soit un 4,
soit un tiret.
Le caractère ^
(accent circonflexe) a un rôle
particulier s'il est placé en début d'intervalle ; il prend
le complémentaire de l'ensemble, il faut le lire "tout caractère sauf ...".
Par exemple [^ao]
matche tout caractère sauf
le a
et le o
. Le motif
[^0-9]
matche tout caractère non numérique.
Nous verrons un peu plus loin qu'il existe des raccourcis pour les ensembles les plus courants.
Les quantificateurs s'appliquent au motif atomique (c'est-à-dire le plus petit possible) le précédant dans l'expression régulière. Ils permettent de spécifier un nombre de fois où ce motif peut/doit être présent.
Par exemple l'étoile *
indique que le motif peut
être présent zéro fois ou plus : m/a*/
se met en correspondance avec le mot vide, avec a
,
aa
, aaa
,
aaaa
...
Quand je dis qu'un quantificateur s'applique au motif atomique le plus
petit possible, je veux dire par là que dans l'expression régulière
m/za*/
l'étoile s'applique uniquement à la lettre
a
et non au mot za
.
Nous verrons plus loin comment faire cela.
Il est par ailleurs important de noter qu'un tel quantificateur est
par défaut gourmand, c'est-à-dire qu'il se met en correspondance avec
le plus de caractères possible dans la variable liée. Cela a son
importance dans le cas d'une substitution : si la variable
$v
contient la chaîne vbaaal
,
et si on effectue l'instruction suivante :
$v =~ s/ba*/hello/;
la chaîne matchée par la première
expression ba*
sera baaa
(le quantificateur matche le plus de caractères possible) et la substitution
aura pour effet de donner pour valeur vhellol
à la variable $v
.
Voici un tableau des quantificateurs :
le motif présent | exemple | mots matchés | |
* |
0 fois ou plus | m/a*/ | mot vide, a , aa , aaa ... |
+ |
1 fois ou plus | m/a+/ |
a , aa , aaa ... |
? |
0 ou 1 fois | m/a?/ | mot vide ou a
|
{ n}
|
n fois exactement | m/a{4}/ | aaaa |
{ n,}
|
au moins n fois | m/a{2,}/ |
aa , aaa , aaaa ... |
{, n}
|
au plus n fois | m/a{,3}/ | mot vide, a , aa ou aaa
|
{ n, m}
|
entre m et n fois | m/a{2,5}/ |
aa , aaa , aaaa ou aaaaa
|
On remarquera que *
est un raccourci pour
{0,}
ainsi que +
pour
{1,}
, de même que ?
pour
{0,1}
.
Dans les exemples précédents, tous les quantificateurs sont
appliqués à un caractère. On peut les appliquer à tout motif, par
exemple à un ensemble : m/[0-9-]{4,8}/
recherche une chaîne comportant entre 4 et 8 caractères numériques
ou tirets contigus.
Nous allons ici énumérer un certain nombre de raccourcis pour des ensembles courants :
\d
: un chiffre, équivalent à [0-9]
(d comme digit, chiffre en anglais)
\D
: un non-numérique, équivalent à [^0-9]
\w
: un alphanumérique, équivalent à [0-9a-zA-Z_]
(w comme word, c'est un caractère d'un mot)
\W
: un non-alphanumérique, équivalent à [^0-9a-zA-Z_]
\s
: un espacement, équivalent à [ \n\t\r\f]
(s comme space)
\S
: un non-espacement, équivalent à [^ \n\t\r\f]
On remarquera qu'un ensemble et son complémentaire sont notés par la même lettre, l'une est minuscule, l'autre majuscule.
Par exemple, l'expression régulière suivante :
m/[+-]?\d+\.\d+/
permet de reconnaître un nombre
décimal, signé ou non : un caractère +
ou -
optionnel, au moins un chiffre, un point
et enfin au moins un chiffre.
Si dans notre exemple précédent, nous souhaitons rendre optionnelle
la partie décimale, on pourrait écrire : m/[+-]?\d+\.?\d*/
rendant ainsi non-obligatoire la présence du point et celle des
chiffres après la virgule. Le problème de cette expression est que la présence
du point et de ces chiffres sont décorrélées : l'expression régulière
reconnaîtra un nombre où l'une de ces deux parties serait présente
et l'autre absente.
Or ce que l'on veut, c'est que le point et les chiffres qui le suivent soient
rendus solidaires dans l'absence ou la présence.
Pour cela nous allons utiliser des parenthèses pour effectuer un regroupement
entre plusieurs motifs (ici le point et les chiffres) pour leur appliquer
conjointement le même quantificateur. L'expression régulière
m/[+-]?\d+(\.d+)?/
reconnaît donc les nombres
tels que nous les souhaitons.
Pour marquer la mémoire de mes étudiants, j'aime à leur dire que
m/meuh{3}/
permet de meugler longtemps
et que m/(meuh){3}/
de meugler
plusieurs fois !
Il est possible d'avoir le choix entre des alternatives ;
il faut pour cela utiliser le signe pipe (|
) :
l'expression m/Fred|Paul|Julie/
reconnaît les mots
comportant soit Fred
, soit Paul
,
soit Julie
.
De la même façon, l'expression m/Fred|Paul|Julie Martin/
reconnaît les chaînes
comportant soit Fred
, soit Paul
,
soit Julie Martin
mais rien n'oblige Fred
à s'appeler Fred Martin
ni Paul
à s'appeler
Paul Martin
, comme on aurait sans doute aimé que cela
se fasse (dans ces deux derniers cas, seul le prénom est reconnu, pas le nom).
Pour cela, vous l'avez compris, un regroupement est nécessaire.
L'expression régulière m/(Fred|Paul|Julie) Martin/
reconnaît les trois frères et soeur de la famille Martin.
Une assertion marque une position dans l'expression, elle ne correspond à aucun caractère (aucune "consommation" de caractères n'est effectuée).
Par exemple, l'accent circonflexe (^
) correspond
au début de la chaîne. L'expression $v =~ m/^a/
est vraie si la variable $v
commence
par la lettre a
.
Le signe ^
a donc plusieurs rôles. S'il est
au début d'un ensemble entre crochets, il permet d'en prendre le
complémentaire ; s'il est au début de l'expression régulière, il
marque le début de la chaîne. On veillera à ne pas les confondre.
Le dollar ($
) correspond à la fin de la chaîne.
L'expression $v =~ m/c$/
est vraie si la variable $v
se termine
par la lettre c
.
Ces deux assertions sont les plus courantes. Il en existe d'autres dont
\b
qui marque un début ou une fin de mot ;
c'est-à-dire entre \w
et /W
(ou entre \w
et une fin ou début de chaîne).
Par exemple m/\btoto\b/
matche
"toto
",
"toto autreMot
",
"unMot toto autreMot
", etc. mais pas
"unMot totoMotCollé
" car le deuxième \b
ne peut pas être vrai entre la lettre o
et la lettre M
.
Le regroupement au moyen des parenthèses est dit mémorisant. Cela signifie que l'expression matchée par ce regroupement est gardé en mémoire par le moteur d'expressions régulières et qu'elle pourra servir à nouveau dans la suite de l'expression.
L'exemple typique consiste à se demander si une variable contient le
même mot répété deux fois. L'expression m/\w+.*\w+/
ne saurait nous satisfaire ; en effet elle matche toute valeur
comportant deux mots pouvant être différents. La solution est
d'utiliser les notations \1
, \2
etc
qui font référence aux sous-chaînes matchées par (respectivement)
la première, la deuxième, etc. expression entre parenthèses
(il n'est pas possible d'accéder à une expression au-delà de
\9
, mais cela nous donne déjà une expression
très lourde à gérer).
Par exemple, la réponse à notre problème de deux occurrences d'un même mot
est la suivante : m/(\w+).*\1/
Le \w+
matchera un mot, les parenthèses mémoriseront
la valeur alors trouvée, le .*
permet comme avant
qu'il y ait un nombre indéfini de caractères quelconques entre
les deux occurrences, enfin \1
fait référence
à la valeur trouvée par le \w+
précédent.
Autre exemple basique, m/(.+), (.+), \2 et \1/
matchera une chaîne de caractères comportant un certain premier motif
suivi d'une virgule et d'une espace, puis un certain second motif
également suivi d'une virgule et d'une espace, puis ce second motif
doit être répété suivi d'une espace, du mot et
puis d'une autre espace et enfin du premier motif.
Ces motifs mémorisés sont aussi accessibles depuis le second membre
d'une substitution au moyen des notations $1
,
$2
etc. Par exemple, l'instruction suivante
$v =~ s/([0-9]+)/"$1"/
place des guillemets
de part et d'autre du premier nombre de la variable
$v
: 'sdq 32sq
'
deviendra 'sdq "32"sq
'.
Vous allez me dire : mais cela signifie que dès que l'on fait un
regroupement, le moteur d'expressions régulières mémorise la valeur
et si l'on n'utilise pas certains regroupements et que d'autres
sont au-delà de 9, on ne peut donc pas s'en servir ...
Je vais alors vous dire : il existe un regroupement non-mémorisant !
La notation
(?:
motifs)
permet de regrouper les motifs (pour leur appliquer le même quantificateur
par exemple) sans pour autant qu'une mémorisation n'ait lieu.
Par exemple m/(.*) (:?et )+(.*) avec \1 \2/
matchera par exemple les valeurs suivantes :
"Paul et Julie avec Paul Julie
"
et "lala et et lili avec lala lili
"
Ces variables spéciales $1
, $2
etc. sont aussi accessibles après l'expression
régulière (jusqu'à la fin du bloc courant ou une autre expression
régulière). Elles correspondent bien sûr aux sous-chaînes matchées
entre parenthèses. Nous pouvons nous en servir pour extraire
certaines sous-chaînes et les utiliser ensuite.
Il existe aussi trois autres variables, dont je vous déconseille l'usage, mais qui peuvent être intéressantes :
$&
vaut toute la sous-chaîne matchant,
$`
vaut toute la sous-chaîne qui précède
la sous-chaîne matchant,
$'
vaut toute la sous-chaîne qui suit
la sous-chaîne matchant.
Je vous déconseille en effet l'usage de ces trois variables spéciales car leur présence dans un script active pour tout le script des mécanismes particuliers dans le moteur d'expressions régulières, qui ont pour effet secondaire d'en ralentir fortement la vitesse d'exécution. Si vous avez besoin de ces variables dans un petit script qui ne sert qu'à cela, pas de problème pour les utiliser, mais évitez leur usage dans un projet de plusieurs milliers de lignes ou dans un script CGI appelé 10 fois par seconde.
Voici un exemple :
my $v = "za aa et tfe"; if( $v =~ /(a+) et ([a-z])/ ) { print "$1\n"; # 'aa' print "$2\n"; # 't' print "$&\n"; # 'aa et t' print "$`\n"; # 'za ' print "$'\n"; # 'fe' }
m//
Je vous ai dit jusqu'ici que l'opérateur de correspondance
m//
retournait vrai ou faux ; cela est exact
en contexte scalaire. C'est par exemple le cas lorsqu'on écrit :
if( $w =~ m/motif/ ) { ... }
On parle alors de correspondance.
Mais en contexte de liste, cet opérateur retourne la liste des éléments
matchés entre parenthèses (les fameux $1
,
$2
etc, et cela sans limite à 9). Par exemple :
($x,$y) = ( $v =~ m/^(A+).*(B+)$/ );
place dans $x
les caractères A
du début de la chaîne $v
et dans
$y
la suite de caractères B
terminant la chaîne. Il se peut tout à fait que cette opération échoue
(en cas d'absence des lettres aux endroits attendus par exemple).
On parle ici d'extraction.
Cet usage peut être combiné avec l'utilisation d'un test. On peut en effet écrire :
if( ($x,$y) = ( $v =~ m/^(A+).*(B+)$/ ) ) { ... }
auquel cas, on n'exécute les instructions du if
que si $v
comporte au moins un A
en son début et un B
à sa fin.
Dans ce cas, les variables $x
et
$y
reçoivent les valeurs entre parenthèses.
On a alors combiné correspondance et extraction.
Dans cette partie de l'article, je vais vous présenter différents petits exercices pratiques sur les expressions régulières. Les solutions se trouvent un peu plus loin. Essayez de ne pas vous précipiter pour les lire, prenez le temps de chercher dans le début de l'article ce qu'il vous faut pour résoudre les problèmes.
Que font les instructions suivantes ?
if( $v =~ m/\w+ \d* ?:/ ) { ... }
if( $v =~ m/^"([a-z]{4,})",/ ) { print "$1\n"; }
if( $v =~ m/([a-z]+)[a-z]*\1/ ) { print "$1\n"; }
($n,$m) = ( $v =~ m/(\w+)=(\d+)/ );
if( ($n,$m) = ( $v =~ m/(\w+)=(\d+)/ ) ) { print "$n $m\n"; }
$v =~ s/^ServerRoot/DocumentRoot/;
$v =~ s/^C="([^"]*)"/D='$1'/;
$v =~ s/ +/ /;
Écrivez les instructions réalisant les actions suivantes :
Vérifier que $v
comporte velo
.
Vérifier que $v
finit par une lettre majuscule.
Vérifier que $v
comporte deux fois de suite un même
nombre (séparées par un signe d'opération mathématique).
Extraire de $v
chacun des deux premiers caractères.
Extraire de $v
les deux premiers mots.
Extraire de $v
le dernier caractère non-numérique.
Remplacer dans $v
rouge
par bleu
.
Supprimer de $v
les espaces en fin de chaîne.
Supprimer les guillemets autour du nombre entier de $v
.
Je relève les copies dans 30 minutes ;-)))
Voici les solutions de la première partie des problèmes :
if( $v =~ m/\w+ \d* ?:/ ) { ... }
On vérifie que $v
comporte un mot d'une lettre
ou plus (\w+
) suivi d'une espace,
puis éventuellement d'un nombre (\d*
), puis d'une espace
optionnelle ( ?
) et enfin du signe deux-points.
Si c'est le cas, les instructions du if
sont exécutées.
if( $v =~ m/^"([a-z]{4,})",/ ) { print "$1\n"; }
On vérifie que $v
commence par un guillemet,
suivi d'au moins quatre lettres minuscules (que l'on mémorise),
d'un autre guillemet puis d'une virgule. Si la variable est du
bon format, ces quatre lettres (ou plus) sont affichées.
if( $v =~ m/([a-z]+)[a-z]*\1/ ) { print "$1\n"; }
On recherche quelque chose de la forme : une suite de
caractères en minuscules (au moins 1) puis une deuxième
suite de caractères en minuscules (éventuellement aucun)
et enfin la même suite de caractères que la première suite.
On cherche donc un mot (suite de lettres) dont un certain
nombre de lettres se répètent. Si la variable $v
comporte un tel mot, on affichera ces lettres répétées.
($n,$m) = ( $v =~ m/(\w+)=(\d+)/ );
On recherche une suite alphanumérique, un signe égal
puis un nombre. Il s'agit d'une affectation. La variable
et le nombre sont respectivement affectés aux variables
$n
et $m
.
if( ($n,$m) = ( $v =~ m/(\w+)=(\d+)/ ) ) { print "$n $m\n"; }
Si la variable $v
est du format précédemment
cité, on affiche la variable et le nombre de l'affectation.
$v =~ s/^ServerRoot/DocumentRoot/;
On remplace ServerRoot
par
DocumentRoot
s'il est en début de chaîne.
$v =~ s/^C="([^"]*)"/D='$1'/;
On recherche en début de chaîne une sous-chaîne
C="
motif"
dont motif ne comporte pas de "
.
Tout cela est remplacé par
D='
motif'
où motif est inchangé.
$v =~ s/ +/ /;
Remplace dans $v
la première suite d'espaces par une seule espace.
Voici les solutions de la seconde partie des problèmes :
Vérifier que $v
comporte velo
.
Pas trop dur : il s'agit d'un simple match.
if( $v =~ m/velo/ ) { ... }
Vérifier que $v
finit par une lettre majuscule.
Match ici aussi. Le dollar nous permet de nous "accrocher" en fin de chaîne :
if( $v =~ m/[A-Z]$/ ) { ... }
Vérifier que $v
comporte deux fois de suite un même
nombre (séparées par un signe d'opération mathématique).
Encore un match. Nous cherchons un nombre \d+
que nous mémorisons (parenthèses). Un signe doit suivre (il faut
déspécifier le signe de la division car il est aussi le séparateur
de l'expression régulière). Finalement
le même nombre qu'avant doit être présent (\1
) :
if( $v =~ m/(\d+)[+*\/-]\1/ ) { ... }
Extraire de $v
chacun des deux premiers caractères.
Nous allons utiliser un match pour faire de l'extraction :
on se place en début de chaîne avec ^
,
on prend un caractère avec .
que l'on mémorise,
puis de la même façon pour le deuxième.
Vous noterez que, dans ce cas, l'usage de la fonction
substr
est possible (et indiqué ...)
($prem,$deux) = ( $v =~ m/^(.)(.)/ );
Extraire de $v
les deux premiers mots.
La méthode est la même que pour l'exemple précédent.
Le seul point un peu délicat à voir, c'est qu'entre deux mots
(\w+
), il doit forcement y avoir
des caractères "non-mot" (\W+
) :
($prem,$deux) = ( $v =~ m/^(\w+)\W+(\w+)/ );
Extraire de $v
le dernier caractère non-numérique.
De la même façon, après le dernier caractère non-numérique, il n'y a que des numériques (zéro ou plus), le dollar pour se placer à la fin de la chaîne :
($c) = ( $v =~ m/(\D)\d*$/ );
Remplacer dans $v
rouge
par bleu
.
Facile (seule la première occurrence est remplacée) :
$v =~ s/rouge/bleu/;
Supprimer de $v
les espaces en fin de chaîne.
On va remplacer tous ces espaces par rien :
$v =~ s/ +$//;
Supprimer les guillemets autour du nombre entier de $v
.
On va faire une substitution, en mémorisant ce fameux nombre :
$v =~ s/"(\d+)"/$1/;
Vous voila parés pour la suite des opérations.
Les fonctionnalités les plus importantes ont été abordées. En voici d'autres qui sont plus poussées et donc plus intéressantes. Les quantificateurs non-gourmands et surtout les options sont des points importants.
Il est tout a fait possible de choisir un autre caractère que le slash
(/
) comme séparateur. Il se peut par exemple que
nous ayons à manipuler des URL ; dans ce cas, le caractère slash
fait partie des motifs que l'on est susceptible de rechercher ; il
est de ce fait fort fastidieux de devoir déspécifier chaque slash utilisé,
comme par exemple dans l'expression suivante (ici une version simplifiée
de l'expression régulière qui reconnaît toutes les URL) :
if( $v =~ m/http:\/\/\w+/(\w+\/)*\w\.html/ )
Il serait plus lisible de prendre un autre séparateur, le signe égal par exemple :
if( $v =~ m=http://\w+/(\w+/)*\w\.html= )
La plupart des caractères est utilisable comme séparateur.
Si vous utilisez le slash comme séparateur, la lettre m
n'est pas obligatoire pour faire un match :
$v =~ /velo/
est équivalent à $v =~ m/velo/
Libre à vous de choisir le bon séparateur, sachant que dans 95% des cas le slash est utilisé.
Après le dernier séparateur des opérateurs de correspondance
(m
ou rien) ou de substitution (s
)
il est possible d'indiquer une ou plusieurs options.
Les syntaxes sont donc :
m/
motif/
options
et
s/
motif1/
motif2/
options
Les options permettent de modifier le comportement du moteur d'expressions régulières. Voici la liste de quelques options parmi les plus utiles :
L'option i
rend le motif insensible à la case
(minuscules/majuscules) : l'expression régulière
m/toto/i
recherche le mot toto indifféremment
en majuscules ou en minuscules. On aurait pu écrire
m/[tT][oO][tT][oO]/
L'option g
permet d'effectuer toutes les
substitutions dans la variables. Par défaut, l'opérateur
s///
effectue la transformation de la première
occurrence du motif recherché et ne va pas plus loin. Si cette option
est spécifiée, le moteur d'expressions régulières avancera dans
la variable tant qu'il pourra y faire des substitutions.
Par exemple, l'expression $v =~ s/ +/ /g;
remplace chaque groupe de plusieurs espaces par une seule
(contrairement à un des exercices précédents où l'expression régulière
ne remplaçait que la première occurrence du motif trouvé).
Voyez par exemple le code suivant :
$t = $s = "sd et sd"; $t =~ s/sd/toto/; # => "toto et sd" $s =~ s/sd/toto/g; # => "toto et toto"
L'option g
est aussi utilisable en correspondance.
Elle permet à cet opérateur de fonctionner avec état, c'est-à-dire
de poursuivre sa recherche en partant du dernier motif trouvé.
On l'utilise typiquement dans une boucle ; voyez cet exemple :
my $v = "aatobbtbvvtczz"; while( $v =~ m/t./g ) { print "$&\n"; }
L'affichage effectué est le suivant :
to tb tc
Dans une substitution, l'option e
évalue le
membre de droite comme une expression Perl, et remplace le motif
trouvé par la valeur de cette expression. Par exemple :
$s =~ s/(\d+)/fonction($1)/e;
remplace le premier nombre trouvé dans la variable $s
par la valeur de retour de la fonction appliquée à ce nombre.
Autre exemple, avec des options combinées celui-là :
$s =~ s/0x([0-9a-f]+)/hex($1)/gei;
transforme tous les nombres hexadécimaux en nombres décimaux
dans la variable $s
.
L'option o
a pour effet qu'une seule compilation
de l'expression régulière a lieu. En temps normal, à chaque fois que
l'interpréteur Perl passe sur une expression régulière, il la compile
(pour ceux qui connaissent, il construit l'automate) ; avec cette
option, la compilation a lieu une seule fois lors de la première
exécution. Le principal avantage est un temps d'exécution plus court
pour le programme, si cette expression est utilisée plusieurs fois.
Les inconvénients sont une place mémoire occupée (inutilement si
l'expression régulière ne sert que peu de fois) et que, si le motif
peut changer (voir la suite concernant les variables dans les motifs),
ce changement ne sera pas pris en compte.
Il existe deux options (exclusives l'une de l'autre) qui permettent de changer
certains comportements sur les débuts et fins de chaînes. Pour les exemples
qui suivent, je pose $s = "mot\nlu";
:
Par défaut : on travaille en ligne unique.
Les caractères ^ $
se positionnent en début/fin de chaîne.
Par exemple ($s=~m/mot$/)
est faux.
Le caractère .
ne matche pas \n
.
Par exemple ($s=~m/t.lu$/)
est faux.
Avec l'option s
: on travaille aussi en ligne unique,
la différence se situe sur la gestion du caractère point.
Les caractères ^ $
se positionnent en début/fin de chaîne.
Par exemple ($s=~m/mot$/s)
est faux.
Le caractère .
peut matcher \n
.
Par exemple ($s=~m/t.lu$/s)
est vrai.
Avec l'option m
: on travaille en ligne multiple.
Les caractères ^ $
se positionnent en début/fin de ligne.
Par exemple ($s=~m/mot$/)
est vrai.
Le caractère .
ne matche pas \n
.
Par exemple ($s=~m/t.lu$/)
est faux.
Posons-nous le problème suivant. Nous avons une chaîne de la forme
"s 'r' g 'e' y"
de laquelle nous
souhaitons extraire les chaînes qui sont entre guillemets. La première
idée est d'écrire quelque chose comme : /'.*'/
ce qui n'est pas satisfaisant, car dans notre exemple la chaîne
'r' g 'e'
serait matchée. En effet, je vous avais
dis que les quantificateurs consomment le plus de caractères possible,
nous voici dans une illustration du phénomène. On parle de quantificateurs
gourmands, gloutons, avides ou greedy en anglais.
Il existe des quantificateurs dont le comportement est, au contraire, de consommer le moins de caractères possible. On parle alors de quantificateurs non-gourmands, économes ou frugaux. Leur notation est la même que celle des quantificateurs que vous connaissez mais suivie d'un point d'interrogation :
Gourmand | Non gourmand |
* |
*? |
+ |
+? |
? |
?? |
{n,m} |
{n,m}? |
Pour revenir à notre exemple, on peut écrire /'.*?'/
et la correspondance sera effectuée telle que nous la souhaitions.
Vous allez me dire, car vous avez tout compris aux expressions régulières ;-) ,
qu'il est possible de faire cela sans utiliser ces quantificateurs non-gourmands.
Dans notre exemple, on peut tout à fait écrire :
/'[^']*'/
ce qui permet de ne pas accepter de guillemets
entre les guillemets. Je répondrais que je suis d'accord avec vous.
Mais voici un autre exemple où les quantificateurs non-gourmands nous
sauvent la mise. Si cette fois la chaîne a pour valeur
"s STARTrSTOP g STARTe fSz zSTOP y"
et que nous souhaitons extraire les sous-chaînes placées en
les marqueurs START
et STOP
,
il nous est fort aisé d'écrire :
/START.*?STOP/
Il est tout à fait possible de mettre une variable dans un motif d'une expression régulière. Cette variable sera substituée par son contenu. Par exemple :
$s = "velo"; if( $v =~ m/$s$/ ) { ... }
La variable sera substituée et la recherche s'effectuera sur le mot
velo
en fin de chaîne ; le premier dollar
concerne la variable $s
, le second marque la fin
de chaîne. Perl sait automatiquement si un $ correspond à une
variable ou à la spécification "fin de chaîne". Il est ainsi
possible de rechercher la valeur d'une variable
dans une autre. La même chose est possible avec la substitution, aussi
bien pour le premier membre que pour le second.
Notez cependant que si la variable substituée contient des caractères
spéciaux au sens des expressions régulières, ils seront vus comme tels.
Si dans notre exemple, la variable $s
avait pour
valeur la chaîne "ve(lo
", le moteur d'expression
régulière nous signalerait une erreur due à la parenthèse ouvrante
qui n'est pas refermée, exactement comme si nous avions écrit :
if( $v =~ m/ve(lo$/ ) { ... } # incorrect
Cela peut aussi poser des problèmes de sécurité si le programmeur ne sait
pas ce que peut contenir la variable substituée (par exemple si sa valeur
provient de l'utilisateur). Il existe pour cela une fonction
quotemeta
qui prend en paramètre une chaîne de caractères
et renvoie cette même chaîne en ayant déspécifié les caractères
spéciaux.
$s = "fds(ds"; $s2 = quotemeta($s); print "$s2\n"; # affiche fds\(ds if( $v =~ m/$s2/ ) { ... }
Pensez à toujours utiliser cette fonction lorsque vous voulez placer une variable dans un motif ; cela résout bien des problèmes.
tr
Cet opérateur ne concerne pas vraiment les expressions régulières, mais il en est proche. Mettez de côté ce que vous venez d'apprendre sur celles-ci pour lire la suite.
tr
est un opérateur de translation lettre à
lettre (on parle de translittération).
Voici sa syntaxe :
tr/
chaîne1/
chaîne2/
ou encore
y/
chaîne1/
chaîne2/
Les deux chaînes doivent être de la même longueur car cet opérateur
va remplacer la première lettre de la première chaîne par la
première lettre de la seconde chaîne, la deuxième lettre de la première
chaîne par la deuxième lettre de la seconde chaîne, etc.
Cette transformation a lieu sur la variable liée ou sur
$_
par défaut.
Voici un petit exemple :
$s = "azerty"; $s =~ tr/abcde/01234/; print "$s\n"; # affiche 0z4rty
Dans la variable $s
, tous les a seront transformés en 0,
tous les b en 1, etc, tous les e en 4 etc.
Il est possible d'utiliser des intervalles :
$s =~ tr/a-z/A-Z/;
met par exemple le contenu de la variable en majuscules.
C'est l'un des seuls usages courants de l'opérateur
tr
.
Les expressions régulières sont un outil très puissant. Les maîtriser ouvre des portes au programmeur. Certes, il est souvent difficile de rentrer dans le jeu, mais cet effort est récompensé par de nouvelles possibilités inimaginables avant.
Les expressions régulières de Pel sont si puissantes et bien pensées que de nombreux langages les implémentent, en se vantant d'être perl5-regexes compliant ! On peut, par exemple, citer la librairie pcre du langage C, dont le nom provient des initiales de "Perl-compatible regular expressions" ...
Sachez aussi que toutes les fonctionnalités des expressions régulières n'ont pas été traitées dans cet article. Les plus courantes ou importantes le sont, mais il en existe d'autres encore ... Sachez de plus que les expressions régulières, c'est bien, mais il faut savoir quand les utiliser et quand ne pas les utiliser.
La prochaine fois nous explorerons les références, qui sont pour Perl ce que sont les pointeurs pour le C ; ce qui nous ouvrira la voie vers la création de modules et la programmation objet.