Formation Perl - Formateur Perl

Formation de qualité par un spécialiste Perl

Sylvain Lhullier

Version 1.4.5
mars 2023


Guide Perl - Débuter et progresser en Perl

Dernière version sur  https://formation-perl.fr/guide-perl.html
© 2002-2023 Sylvain Lhullier - Permission est accordée de copier et distribuer ce document sans modification et à condition de fournir un lien vers la page https://formation-perl.fr/guide-perl.html

> Envie d'une formation Perl ? Sylvain Lhullier propose des formations au langage Perl, contactez-le.

8. Expressions régulières

Nous abordons ici 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 cette partie du document peut aussi intéresser toute personne utilisant grep, sed, Python, PHP, C, C++ 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 mœurs et sera donc utilisée ici.

On retrouve les expressions régulières dans certaines fonctions Perl que vous connaissez déjà, comme split ou grep ; mais elles existent aussi par le biais d'opérateurs spécifiques.

8.1. 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 cinq 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/motif/chaîne/

Le s indique que nous voulons faire une substitution, les slashes (/) servent à délimiter le motif recherché ainsi que la chaîne de remplacement.

8.2. 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 $v 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.

8.3. Caractères

Dans cette sous-partie et dans les suivantes, 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 ce document, 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 :

Tableau 3. 

MotifCaractère
\nsaut de ligne
\rretour chariot
\ttabulation
\fsaut de page
\eéchappement

Seuls les plus utiles sont présentés ici.

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

8.5. 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 que 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 :

Tableau 4. 

 le motif présentexemplemots matchés
*0 fois ou plusm/a*/mot vide, a, aa, aaa...
+1 fois ou plusm/a+/a, aa, aaa...
?0 ou 1 foism/a?/mot vide ou a
{n}n fois exactementm/a{4}/aaaa
{n,}au moins n foism/a{2,}/aa, aaa, aaaa...
{,n}au plus n foism/a{,3}/mot vide, a, aa ou aaa
{n,m}entre m et n foism/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 quatre et huit caractères numériques ou tirets contigus.

8.6. Ensembles (suite)

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.

8.7. 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 !

8.8. 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 sœur de la famille Martin.

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

8.10. 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ée 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 lili avec lala lili"

8.11. 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 :

  • $& 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é dix 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'
}

Il est bon de savoir que cela est possible sans obligatoirement se souvenir du nom de toutes les variables.

8.12. 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. On parle ici d'extraction.

Il se peut tout à fait que cette opération échoue (en cas d'absence des lettres aux endroits attendus par exemple). 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.

8.13. Exemples de problèmes

Dans cette partie, 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 ce qui précède ce qu'il vous faut pour résoudre les 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é 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 ;-)))

8.14. Solutions des problèmes

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+(\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 toutes 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/;
    

Les fonctionnalités les plus importantes ont été abordées vous voilà parés pour la suite des opérations. Voici d'autres fonctionnalités plus poussées et donc plus intéressantes. Les quantificateurs non gourmands et surtout les options sont des points importants.

8.15. Choisir son séparateur

Il est tout à 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é, par exemple dans l'expression suivante (ici une version simplifiée de l'expression régulière qui reconnaît 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 la grande majorité des cas le slash est utilisé.

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

  • L'option i rend le motif insensible à la casse (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 variable. 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 : mode intermédiaire.

    • 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 en ligne unique.

    • 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$/m) est vrai.

    • Le caractère . ne matche pas \n. Par exemple ($s=~m/t.lu$/m) est faux.

8.17. 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 dit 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 :

Tableau 5. 

GourmandNon 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épondrai 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 entre les marqueurs START et STOP, il nous est fort aisé d'écrire : /START.*?STOP/

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

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

8.20. Un dernier mot sur les expressions régulières

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 Perl 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 bibliothèque 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 ici. 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.


Contact © 1999-2024 formation-perl.fr
Formation Perl - Formation Perl intra entreprise - Formation Perl inter entreprise - Formateur Perl - Formateur Perl délégation
Formation Perl Formateur Perl Appartement à louer à Noisy-le-Grand Bric-à-Brac