Les expressions régulières5.1 sont une des caractéristiques de perl qui rendent ce langage particulièrement adapté au traitement des fichiers texte.
Une expression régulière5.2 est une suite de caractères suivant une certaine syntaxe qui permet de décrire le contenu d'une chaîne de caractères, afin de tester si cette dernière correspond à un motif5.3, d'en extraire des informations ou bien d'y effectuer des substitutions.
Elle est à la base identique à celle des expressions régulières de programmes connus comme grep, sed,... mais plusieurs nouvelles fonctionnalités ont été ajoutées. Nous allons voir ici les bases, ainsi que certaines améliorations apportées par perl.
Les opérations sur les expressions régulières sont effectuées par
défaut sur la variable $_
.
Pour les faire s'appliquer à une autre variable, il faut utiliser
l'opérateur =~
:
if ($variable =~ /regexp/) {...}; # est donc équivalent à $_ = $variable; if (/regexp/) {...};
Chaque caractère correspond à lui-même, exception faite des métacaractères qui ont une signification particulière. Pour traiter un métacaractère comme un caractère normal, il suffit de le précéder d'un \.
Voici quelques métacaractères, avec leur signification :
^ |
début de chaîne. Ce n'est pas un véritable caractère ; |
$ |
fin de chaîne. Même remarque ; |
. |
n'importe quel caractère, excepté newline ; |
| |
alternative (à placer entre parenthèses) ; |
() |
groupement et mémorisation ; |
[] |
classe de caractères. |
Quelques explications sont peut-être nécessaires. Une regexp du
type /a[bc]d/
correspondra aux chaînes abd et
acd. [] permet d'énumérer une classe de
caractères. L'interprétation de cette expression sera donc : un
a, suivi d'un b ou d'un c, puis un
d.
L'ensemble des caractères composant la classe peut être précisée par
énumération (comme précédement) ou bien en précisant un intervalle
comme par exemple /[a-z]/
qui correspondra à tous les
caractères compris entre a et z.
On peut également prendre le complémentaire de cet ensemble, en le
précédant d'un ^5.4. Donc /a[^bc]d/
correspondra à toutes les chaînes du type
a.d, sauf abd et acd. On peut donc lire :
un a, suivi d'un caractère qui n'est ni un b ni un
c, puis un d.
L'alternative permet de préciser que l'on recherche l'une ou l'autre
des expressions séparées par des |
. Par exemple, /arti(chaut|ste)/
correspondra aux chaînes
artichaut et artiste. Il est bien sûr possible de
mettre plus de deux alternatives.
Enfin, la mémorisation permet de mémoriser certaines des parties de la
chaîne. Par exemple, appliquer l'expression /b(.+)ars/
à
la chaîne beggars mémorisera la partie qui correspond à ce
qui se trouve entre parenthèses, i.e. egg, et fixera la
variable $1 à cette valeur. On peut donc lire : un b,
suivi d'une série (+) de caractères quelconques (.),
que l'on mémorisera (les parenthèses), puis la chaîne ars.
Les notations suivantes sont également utilisables (pour la plupart inspirées de la syntaxe de la fonction C printf) :
t | tabulation |
n | newline |
r | retour-chariot |
e | escape |
cC | contrôle-C , où C peu être n'importe
caractère. |
s | espace |
S | non-espace |
w | lettre (caractères alphanumériques et _) |
W | non-lettre |
d | chiffre |
D | non-chiffre |
Différents quantificateurs s'appliquent aux caractères et métacaractères :
* |
apparaît 0, 1 ou plusieurs fois |
+ |
apparaît 1 ou plusieurs fois |
? |
apparaît 0 ou 1 fois |
{n} |
apparaît exactement n fois |
{n,} |
apparaît n fois ou plus |
{m,n} |
apparaît au moins m fois, au plus n fois |
Il faut savoir que ces quantificateurs sont dits gourmands. Par
exemple, appliquer /a(.+)a/
à abracadabra fixera
$1
à bracadabr, la plus longue chaîne possible correspondant à l'expression, et non br.
Une nouveauté intéressante introduite dans la version 5 de perl est
la possibilité d'obtenir des quantificateurs non
gourmands, i.e. qui ne matchent pas la plus
grande chaîne possible, en mettant un ?
après le
quantificateur.
Voici un exemple illustrant le caractère gourmand des expressions régulières classiques, et l'apport de perl5 dans ce domaine :
$chaine = 'Voila un <A HREF="index.html">index</A> et une autre <A HREF="reference.html">reference</A>.'; ($greedy) = ($chaine =~ /(<.+>)/); ($nongreedy) = ($chaine =~ /(<.+?>)/); print "1: ", $greedy, "\n2: ", $nongreedy, "\n";qui donne le résultat suivant :
1: <A HREF="index.html">index</A> et une autre <A HREF="reference.html">reference</A> 2: <A HREF="index.html">
Une des premières utilisations des expressions régulières est le matching : on peut tester si une chaîne de caractères correspond à une expression régulière, i.e. à un motif particulier.
Pour cela, on utilise la fonction m/REGEXP/, qui
s'applique par défaut à la variable $_
. Si on désire
l'appliquer à une autre variable, la syntaxe est la suivante:
if ($var =~ m/REGEXP/) { ... }
REGEXP est l'expression régulière dont on cherche à
savoir si elle correspond à la variable $
var.
On peut faire suivre cette fonction de paramètres qui modifient le comportement de la fonction de matching. Ces paramètres sont formés d'au moins un caractère parmi g, i, s, m, o, x. Voici le détail de leurs actions :
g | recherche globale (recherche toutes les occurences, s'il y en a plusieurs dans la chaîne traitée) |
i | ne pas tenir compte de la casse des caractères (case-insensitive) |
s | traiter la chaîne comme une ligne simple (défaut) |
m | traiter la chaîne comme une ligne multiple |
o | ne compiler l'expression qu'une seule fois |
x | utiliser les expressions régulières étendues |
Pour tester si la variable $var contient la chaîne foo mais sans faire de distinction majuscules/minuscules, on écrira :
if ($var =~ m/foo/i) { ... }
Le premier m de l'expression m/REGEXP/ peut être omis si le délimiteur de l'expression est un / (slash, ou barre oblique). L'utilisation du m permet d'utiliser n'importe quel caractère comme délimiteur, ce qui évite, lorsqu'il s'agit de construire une expression contenant des /, de précéder chaque / d'un . Par exemple :
if ($var =~ m#^/etc#) { ... }
Les expressions régulières peuvent contenir des variables qui seront interpolées à chaque appel à l'expression. Ce comportement est utile, et intuitif, dans certaines situations mais est pénalisant du point de vue de la rapidité d'exécution. Si l'on est certain que la variable ne changera pas au cours du script, on peut ajouter le modificateur o. Mais attention, si on oublie que l'on a mis ce modificateur et que l'on modifie quand même la variable, ce changement ne sera pas pris en compte dans l'expression régulière.
La valeur retournée par la fonction dépend du contexte dans lequel elle est appelée : dans un contexte scalaire, la fonction renvoie une valeur non nulle en cas de succès, et une valeur undefined dans le cas contraire.
$match = ($chaine =~ /regexp/);
Dans un contexte de liste, la fonction renvoie la liste des éléments qui ont matché les expressions entre parenthèses. Si l'expression ne correspondait pas, on obtient une liste nulle.
($href) = ($chaine =~ /<a\s+href="(.+?)">/i);
Dans tous les cas, la fonction fixera les variables 2, ... avec les éléments qui ont matché les expressions entre parenthèses.
La syntaxe de la fonction de substitution est analogue à celle
utilisée par sed, vi, ..., mises à part les quelques
différences syntaxiques d'écriture des regexps. De manière analogue à
la fonction de comparaison, la fonction de substitution s'applique par
défaut à la variable $_
. Pour la faire s'appliquer à une
variable quelconque, il faut utiliser la notation :
$var =~ s/REGEXP/chaîne/egismox;
Les modificateurs qui suivent l'expression sont identiques à ceux applicables à la fonction de comparaison.
Un nouveau modificateur est disponible : e. Il précise que l'expression de remplacement est une expression à évaluer, ce qui permet d'utiliser le résultat d'une opération dans la chaîne de remplacement.
Par exemple, pour convertir des caractères du type %
xx où
xx est un code ASCII en hexadécimal dans le caractère
correspondant, il suffit d'écrire:
$chaine =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
Une petite explication ? On veut effectuer une substitution (s/.../.../) sur la variable $chaine. On cherche un caractère % suivi de deux caractères valides en hexadécimal ([a-fA-F0-9]). On mémorise ces deux caractères, dans la variable $1 puis on remplace les trois caractères (les deux mémorisés plus le % qui les précéde) par le résultat de la fonction pack("C", hex($1)), qui évalue le contenu de $1 en hexadécimal, puis affiche le caractère correspondant à cette valeur. On effectue cette opération tant qu'il reste des possibilités (le modificateur g).
Prenez votre temps pour essayer de comprendre le fonctionnement de cette expression régulière. Ensuite, essayez de comprendre les transformations qui permettent d'arriver à l'expression équivalente :
$chaine =~ s/%([a-f0-9]{2})/pack("C", hex($1))/egi;
Voilà une dernière fonction qui est généralement associée aux
expressions régulières, même si elle ne les utilise pas. Son
point commun avec les fonctions précédentes est qu'il faut
utiliser =~
pour préciser à quelle variable elle s'applique ($_
par défaut).
Elle a le même comportement que l'utilitaire UNIX tr. Sa syntaxe est la suivante :
$variable =~ tr/liste1/liste2/cds;
Par défaut, elle transpose chaque caractère de liste1 en le caractère correspondant de liste2. Trois modificateurs sont disponibles :
c | complémente la liste liste1 |
d | supprime les éléments de liste1 qui n'ont |
pas de correspondance dans liste2 | |
s | compresse les caractères dupliqués qui sont en double |
Pour supprimer par exemple tous les caractères non alphanumériques, on écrit :
$chaine =~ tr/a-zA-Z0-9.//cd;