Introduction à la programmation en Perl

Quatrième partie : les expressions régulières

© 2002 - Sylvain Lhullier
Paru dans LinuxMagazine-France en novembre 2002

Chapeau de l'article

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.

Introduction

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.

Généralités

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.

Fonctionnalités

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.

Bind

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.

Motifs

Dans cette partie nous allons voir quels sont les motifs utilisables dans les expressions régulières.

Caractè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

Ensembles

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.

Quantificateurs

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.

Ensembles (suite)

Nous allons ici énumérer un certain nombre de raccourcis pour des ensembles courants :

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.

Regroupement

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 !

Alternatives

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.

Assertions

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.

Références arrières

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"

Variables définies

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 :

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'
}

Valeurs de retour de 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.

Exemples

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.

Problèmes

Que font les instructions suivantes ?

  1. if( $v =~ m/\w+ \d* ?:/ )
    { ... }
    
  2. if( $v =~ m/^"([a-z]{4,})",/ )
    {  print "$1\n"; }
    
  3. if( $v =~ m/([a-z]+)[a-z]*\1/ )
    {  print "$1\n"; }
    
  4. ($n,$m) = ( $v =~ m/(\w+)=(\d+)/ );
    
  5. if( ($n,$m) = ( $v =~ m/(\w+)=(\d+)/ ) )
    { print "$n $m\n"; }
    
  6. $v =~ s/^ServerRoot/DocumentRoot/;
    
  7. $v =~ s/^C="([^"]*)"/D='$1'/;
    
  8. $v =~ s/ +/ /;
    

Écrivez les instructions réalisant les actions suivantes :

  1. Vérifier que $v comporte velo.

  2. Vérifier que $v finit par une lettre majuscule.

  3. Vérifier que $v comporte deux fois de suite un même nombre (séparées par un signe d'opération mathématique).

  4. Extraire de $v chacun des deux premiers caractères.

  5. Extraire de $v les deux premiers mots.

  6. Extraire de $v le dernier caractère non-numérique.

  7. Remplacer dans $v rouge par bleu.

  8. Supprimer de $v les espaces en fin de chaîne.

  9. Supprimer les guillemets autour du nombre entier de $v.

Je relève les copies dans 30 minutes ;-)))

Solutions

Voici les solutions de la première partie des problèmes :

  1. 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.

  2. 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.

  3. 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.

  4. ($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.

  5. 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.

  6. $v =~ s/^ServerRoot/DocumentRoot/;
    

    On remplace ServerRoot par DocumentRoot s'il est en début de chaîne.

  7. $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'motif est inchangé.

  8. $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 :

  1. Vérifier que $v comporte velo.

    Pas trop dur : il s'agit d'un simple match.

    if( $v =~ m/velo/ )
    { ... }
    
  2. 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]$/ )
    { ... }
    
  3. 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/ )
    { ... }
    
  4. 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/^(.)(.)/ );
    
  5. 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+)/ );
    
  6. 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*$/ );
    
  7. Remplacer dans $v rouge par bleu.

    Facile (seule la première occurrence est remplacée) :

    $v =~ s/rouge/bleu/;
    
  8. Supprimer de $v les espaces en fin de chaîne.

    On va remplacer tous ces espaces par rien :

    $v =~ s/ +$//;
    
  9. 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.

Autres fonctionnalités

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.

Choisir son séparateur

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é.

Options

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 :

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"; :

Quantificateurs non-gourmands

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/

Substitution de variables dans les motifs

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.

Opérateur 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.

Conclusion

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.