Programmation Perl/Expressions régulières

Les expressions régulières sont des outils de l'informatique théorique permettant de décrire les langages de manière formelle. Elles sont implémentées dans beaucoup de langages de programmation dont Perl qui les utilisent avec une syntaxe étendue (fonction, code dans l'expression régulière, ...).

Programmation Perl
Programmation Perl
Programmation Perl
Sommaire




Modifier ce modèle


Introduction

modifier

Ce chapitre peut vous être utile peu importe quel langage vous avez l'habitude de manipuler ou que vous utiliserez à l'avenir. N'hésitez pas à lire ce chapitre en plusieurs fois pour apprécier toutes les subtilités et pour comprendre à quel point ceci fait partie intégrante du langage Perl et comment bien se servir de ces outils.

Définition

modifier

Une expression régulière est une manière de manipuler du texte de façon concise et claire à l'aide de motifs. Les expressions régulières (ou « rationnelles », aussi appelées « regex » pour « regular expression ») sont issues des théories mathématiques sur les langages formels.

Tout d'abord, nous utilisons le symbole =~ pour utiliser des expressions régulières, ensuite nous avons toute une panoplie de fonctionnalités à appliquer sur un texte (recherche, remplacement…), avec des options disponibles. Pour cela, voici la syntaxe :

$texte =~ fonction_voulue/première zone de texte[/seconde zone de texte si la fonction voulue le requiert]/[options];

Pour rappel, ce qui est entre crochets [] n'est pas toujours obligatoire. Le remplacement de texte par exemple nécessitera de rechercher du texte (première zone de texte) puis de le remplacer par un autre texte (seconde zone de texte). Pour la recherche de texte, pas besoin d'indiquer la seconde zone.

À savoir : cet exemple utilise des slashs (/) cependant nous pouvons utiliser d'autres caractères, comme l'underscore, un point d'exclamation, ou encore des accolades par exemple. Ceci est pratique lorsque nous avons à chercher un motif contenant des slashs (pas besoin de le banaliser, c'est à dire rajouter un antislash « \ » devant le caractère).

Exemple

modifier

Un exemple simple d'utilisation serait de chercher un mot dans un texte, fonctionnalité m (Match en anglais) :

my $texte = "J'aime le fromage";
# la fonctionnalité "m" correspond à la recherche de motif
if( $texte =~ m/fromage/ ) # on n'utilise pas d'options
{
	say "On a trouvé « fromage » dans le texte";
}
else
{
	say "On n'a pas trouvé de fromage :(";
}

Comme on peut s'y attendre, ce code va afficher « On a trouvé « fromage » dans le texte » puisque nous recherchons le mot « fromage » dans $texte. En fait, la fonction cherche à savoir si le texte correspond (match en anglais) au motif spécifié. Celui-ci ne contenant pas d'ancre de début ou fin de texte, peut correspondre avec une partie se trouvant au milieu du texte.

Fonctionnalités

modifier

Les expressions rationnelles peuvent être analysées et testées via un débogueur en ligne comme https://regex101.com/.

Expressions rationnelles courantes
Caractère Type Explication
. Point N'importe quel caractère
[...] crochets classe de caractères : tous les caractères énumérés dans la classe, avec possibilité de plages dont les bornes sont séparées par "-". Ex : [0-9a-z] pour tout l'alphanumérique en minuscule, ou [0-Z] pour tous les caractères de la table Unicode entre "0" et "Z", c'est-à-dire l'alphanumérique majuscule plus ":;<=>?@"[1].
[^...] crochets et circonflexe classe complémentée : tous les caractères sauf ceux énumérés.
[...[...]] union Union des deux ensembles
[...&&[...]] intersection Intersection des deux ensembles
^ circonflexe Marque le début de la chaîne ou de la ligne.
$ dollar Marque la fin de la chaîne ou de la ligne.
| barre verticale Alternative - ou reconnaît l'un ou l'autre
(...) parenthèses groupe de capture : utilisé pour limiter la portée d'un masque ou de l'alternative, grouper un motif répété ou capturer une séquence
\n référence Même séquence que celle capturée précédemment par le nème groupe de capture
\g{n} référence Même séquence que celle capturée précédemment par le nème groupe de capture
(?P<nom>pattern) Sous-motif nommé Nomme le résultat d'un groupe de capture par un nom.
\g{nom} référence Même séquence que celle capturée précédemment par le groupe de capture nommé nom.
\k<nom> référence Même séquence que celle capturée précédemment par le groupe de capture nommé nom.

Par défaut, les caractères et groupes ne sont pas répétés. Les quantificateurs permettent de spécifier le nombre de répétitions et sont spécifiés immédiatement après le caractère ou groupe concerné.

Quantificateurs
Caractère Type Explication
* astérisque 0, 1 ou plusieurs occurrences
+ plus 1 ou plusieurs occurrences
? interrogation 0 ou 1 occurrence
{...} accolades nombre de répétitions : spécifie le nombre de répétitions du motif précédent (minimum et maximum). Avec la présence de la virgule, quand le minimum est absent la valeur par défaut est zéro, quand le maximum est absent la valeur pas défaut est l'infini. Sans virgule (un seul nombre) il s'agit du nombre exact (minimum et maximum ont la même valeur). Exemples :
  • a{2} deux occurrences de "a",
  • a{1,10} (sans espace) entre une et dix,
  • a{,10} jusqu'à 10 fois (de 0 à 10),
  • a{3,} au moins 3 fois (de 3 à l'infini).

Par défaut les quantificateurs ne recherchent pas forcément la plus longue séquence de répétition possible. Il est possible de les suffixer avec un caractère pour modifier leur comportement.

Modificateurs de quantificateurs
Caractère Type Explication
? réticent Le quantificateur qui précède recherchera la plus petite séquence possible.
+ possessif Le quantificateur qui précède recherchera la plus grande séquence possible.

Remarques :

  • Les caractères de début et fin de chaîne (^ et $) ne fonctionnent pas dans [] où ils ont un autre rôle.
  • Les opérateurs * et + sont toujours avides, pour qu'ils laissent la priorité il faut leur apposer un ? à leur suite[2].
Classes de caractères POSIX[3]
Classe Signification
[[:alpha:]] n'importe quelle lettre
[[:digit:]] n'importe quel chiffre
[[:xdigit:]] caractères hexadécimaux
[[:alnum:]] n'importe quelle lettre ou chiffre
[[:space:]] n'importe quel espace blanc
[[:punct:]] n'importe quel signe de ponctuation
[[:lower:]] n'importe quelle lettre en minuscule
[[:upper:]] n'importe quelle lettre capitale
[[:blank:]] espace ou tabulation
[[:graph:]] caractères affichables et imprimables
[[:cntrl:]] caractères d'échappement
[[:print:]] caractères imprimables exceptés ceux de contrôle
Expressions rationnelles Unicode[4]
Expression Signification
\\ Antislash
\C Caractère spécial C non interprété : [ ] { } ( ) ? * . : \ & - ^ $
\Q...\E Séquence littérale non interprétée
\0xxx Caractère Unicode (1 à 3 chiffres octaux)
\a Alarme (ASCII 07)
\A Début de chaîne
\b Caractère de début ou fin de mot
\B Caractère qui n'est pas début ou fin de mot
\cX Caractère de contrôle ASCII (X étant une lettre)
\d Chiffre
\D Non chiffre
\e Escape (ASCII 1B)
\f Form-feed (ASCII 0C)
\G Fin de la correspondance précédente
\h Espace blanc horizontal [ \t\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]
\H Non espace blanc horizontal [^\h]
\n Fin de ligne
\pL, \p{L}, \p{Letter} Lettre (dans tout langage)
\r Retour charriot
\R Retour à la ligne, équivaut à \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]
\s Caractères espace [ \t\n\x0B\f\r]
\S Non caractères espace [^\s]
\t Tabulation
\uxxxx Caractère Unicode (4 chiffres hexadécimaux)
\v Espace blanc vertical [\n\x0B\f\r\x85\u2028\u2029]
\V Non espace blanc vertical [^\v]
\w Caractère alphanumérique : lettre, chiffre ou underscore
\W Caractère qui n'est pas lettre, chiffre ou underscore
\xxx Caractère Unicode (2 chiffres hexadécimaux)
\x{xx...x} Caractère Unicode (chiffres hexadécimaux)
\X Caractère Unicode du groupe de graphèmes étendu
\z Fin de chaîne

Constructeurs spéciaux : Ces fonctions précèdent l'expression à laquelle elles s'appliquent, et le tout doit être placé entre parenthèses.

  • ?: : groupe non capturant. Ignorer le groupe de capture lors de la numérotation des backreferences. Exemple : ((?:sous-chaine_non_renvoyée|autre).*).
    La présence d'un groupe capturant peut engendrer une allocation mémoire supplémentaire. Si une expression régulière particulièrement complexe provoque une erreur de mémoire, essayez de remplacer les groupes capturant non référencés et inutilisés par des groupes non-capturant en ajoutant ?: juste après la parenthèse ouvrante, et en décalant les numéros des groupes référencés.
  • ?> : groupe non capturant indépendant.
  • ?<= : positive lookbehind, vérifier (sans consommer) que ce qui précède correspond au motif spécifié. Exemple :
    Chercher une lettre u précédée d'une lettre q : (?<=q)u
  • ?<! : negative lookbehind, vérifier (sans consommer) que ce qui précède ne correspond pas au motif spécifié.
  • ?= : positive lookahead, vérifier (sans consommer) que ce qui suit correspond au motif spécifié.
  • ?! : negative lookahead, vérifier (sans consommer) que ce qui suit ne correspond pas au motif spécifié. Exemples :
    Chercher une lettre q non suivie d'une lettre u : q(?!u)
    ((?!sous-chaine_exclue).)
    <(?!body).*> : pour avoir toutes les balises HTML sauf "body".
    début((?!mot_exclu).)*fin[5] : pour rechercher tout ce qui ne contient pas un mot entre deux autres.
    (?!000|666) : pour exclure 000 et 666[6].

Options :

Les options d'interprétation sont en général spécifiées à part. Mais certaines API ne permettent pas de les spécifier. Il est possible d'insérer ces options dans l'expression régulière[7].

(?optionsactivées-optionsdésactivées)

Exemples :

  • Chercher un mot composé de voyelles sans tenir compte de la casse :
    (?i)[AEIOUY]+
  • Chercher un mot composé de voyelles en tenant compte de la casse, ici en majuscules :
    (?-i)[AEIOUY]+

Les options s'appliquent à toute l'expression quelle que soit leur position dans l'expression.


Nous allons faire un tour des fonctionnalités (que j'appellerai également modes) les plus utilisés.

recherche : m

modifier

Pour effectuer des recherches dans une chaîne de caractère, nous utilisons la fonctionnalité m. C'est le mode par défaut, nous n'avons pas besoin de l'écrire.

#!/usr/bin/perl -w
use strict;

# ^ début de ligne 
@tableau = ("affiche", "âme", "à toi", " affiche");
print 'Test sur la regex : if ( $texte =~ m/^a/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/^a/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "(espace en-tête)";
print "\n" x 2;

# $ fin de ligne 
@tableau = ("affiche", "affiche.", "affiches");
print 'Test sur la regex : if ( $texte =~ m/che.$/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/che.$/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n" x 2;

# \. point échappé
@tableau = ("affiche", "affiche.", "affiches");
print 'Test sur la regex : if ( $texte =~ m/che\.$/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/che\.$/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print '(\. point échappé = vrai point, pas n‘importe quel caractère)';
print "\n" x 2;

# {…}
@tableau = qw(tttrès hutte haute hausse tousse);
print 'Test sur la regex : if ( $texte =~ m/[t]{1,2}/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/t{2,3}/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

remplacement : s

modifier

Le mode s permet d'effectuer un remplacement (aussi appelé substitution) dans une chaîne. Ici, nous avons besoin d'utiliser les deux zones de textes, de cette manière : s/texte à changer/nouveau texte/ .

exemple de substitution

modifier
my $texte = "J'aime le fromage.";
$texte =~ s/le fromage/les légumes/; # les légumes c'est plus sain que le fromage !
say $texte; # affiche « J'aime les légumes. »

stocker une expression régulière : qr

modifier
my $regex_mail = qr{[\w-+.]+@[\w-]+(?:\.[\w-]+)+};
unless ("nom@example.com" =~ /$regex_mail/) {
	say "Ce n'est pas une adresse mail";
}

constructeurs spéciaux :

modifier

?= préviseur positif (positive lookahead)

modifier
#!/usr/bin/perl -w
use strict;
my $texte = "anticonstitutionnellement";

# On cherche un "ti" qui précède ("il prévoit") "on" (suite affichée pour la clarté)
$texte =~ /(ti(?=on).+)/;# 
print $1."\n"; # tionnellement

# un "ti" qui précède "tu"
$texte =~ /(ti(?=tu).+)/;# 
print $1."\n"; # titutionnellement

?! préviseur négatif (negative lookahead)

modifier
#!/usr/bin/perl -w
use strict;
my $texte = "anticonstitutionnellement";

# On cherche un "ti" qui NE précède PAS "on" (suite affichée pour la clarté)
$texte =~ /(ti(?!on).+)/;# 
print $1."\n"; # ticonstitutionnellement

# un "ti" qui NE précède PAS "cons"
$texte =~ /(ti(?!cons).+)/;# 
print $1."\n"; # titutionnellement

?<= rétroviseur positif (positive lookbehind)

modifier
#!/usr/bin/perl -w
use strict;
$texte = "anticonstitutionnellement";

# On cherche un "ti" qui suit "tu" (début affiché pour la clarté)
$texte =~ /(.+(?<=tu)ti)/;#
print $1."\n"; # anticonstituti

# un "ti" qui suit "an"
$texte =~ /(.+(?<=an)ti)/;#
print $1."\n"; # anti

?<! rétroviseur négatif (negative lookbehind)

modifier
#!/usr/bin/perl -w
use strict;
$texte = "anticonstitutionnellement";

# On cherche un "ti" qui NE suit PAS "tu" (début affiché pour la clarté)
$texte =~ /(.+(?<!tu)ti)/;#
print $1."\n"; # anticonsti

# un "ti" qui NE suit PAS "an"
$texte =~ /(.+(?<!an)ti)/;#
print $1."\n"; # anticonstituti

?> constructeur tête de mule !

modifier

Attrape la chaîne correspondante maximale où il est ancré, et refuse de faire un pas en arrière

( pas de backtracking pour trouver toutes les sous-chaînes ).

#!/usr/bin/perl -w
use strict;

my $count=0;
my $texte="a"x 33 ."b";
# on cherche les séries a…ab & les sous-séries a…a

# sans ?>
$texte =~ /(a+b?)(?{print "$&\n"; $count++})(*FAIL)/;
print "Count=$count\n"; # 594

# avec ?>
$count=0;
$texte =~ /(?>(a+b?))(?{print "$&\n"; $count++})(*FAIL)/;
print "Count=$count\n"; # 33

Sert à l'optimisation :

Pour 2 000 "a", on attend 2 003 000 recherches (sans ?>) contre 2 000 seulement (avec ?>).

Correspondances

modifier

Lors d'une recherche de motif, nous pouvons utiliser des caractères spéciaux (parfois appelés « méta-caractères ») pour rechercher un type de caractère (et non un caractère explicitement).

caractères et expressions de correspondance

modifier

caractère quelconque : .

modifier

Le point « . » correspond à un caractère quelconque. Lors d'une recherche, nous pouvons l'utiliser pour indiquer n'importe quel caractère, excepté le retour à la ligne (\n).

exemple utilisation du point
modifier
#!/usr/bin/perl -w
use strict;

# le point .
my @tableau = qw(matin mâtin méthode admit);
print 'Test sur la regex : if ( $texte =~ m/m.t/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/m.t/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "(lettres accentuées = 2 octets)";
print "\n" x 2;

# double point ..
@tableau = qw(matin mâtin méthode admit);
print 'Test sur la regex : if ( $texte =~ m/m..t/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/m..t/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

caractère alphabétique : [[:alpha:]]

modifier

Pour rechercher un caractère alphabétique on utilise: [[:alpha:]] ou éventuellement [a-zA-Z].

#!/usr/bin/perl -w
use strict;

# [[:alpha:]]
my @tableau = qw(satin 264 mâtin àâéèêëîôùûæœç àâéèêëîôùûæœça);
print 'regex : if ( $texte =~ m/[[:alpha:]]/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/[[:alpha:]]/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print '(voyelles accentuées non ASCII)';
print "\n";

caractère numérique : \d et [[:digit:]]

modifier

Nous avons deux possibilités pour rechercher un caractère numérique : [[:digit:]] et \d. Pour chercher un caractère qui n'est pas un chiffre : \D.

#!/usr/bin/perl -w
use strict;

# [[:digit:]]
my @tableau = qw(satin 264 37.5lematin àâ68éèêëîôùûæœç);
print 'regex : if ( $texte =~ m/[[:digit:]]/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/[[:digit:]]/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

caractère hexadécimal : [[:xdigit:]]

modifier

Cela correspond à tout caractère hexadécimal, c'est à dire tous les caractères numériques (de 0 à 9) et les lettres de A à F.

#!/usr/bin/perl -w
use strict;

# [[:xdigit:]]
my @tableau = qw(sAtin DEBAClE 0123456789ABCDEF abcdef ghijkl);
print 'regex : if ( $texte =~ m/[[:xdigit:]]/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/[[:xdigit:]]/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

caractère alphanumérique : [[:alnum:]] et \w

modifier

Les caractères alphanumériques correspondent à l'ensemble des caractères numériques (0,1,2…) et aux lettres. La classe \w reconnaît, en plus de [[:alnum:]], le caractère «_». Pour chercher un caractère qui n'est pas un nombre ou une lettre ou «_» : \W.

#!/usr/bin/perl -w
use strict;

# non [[:alnum:]] = \W
my @tableau = qw(&§@$£()[]{}#°%_ sAtin 0123456789ABCDEF §ghijkl);
print 'regex : if ( $texte =~ m/\W/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/\W/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

caractère d'espacement simple : [[:blank:]]

modifier

Un espacement correspond à un espace, une tabulation.

#!/usr/bin/perl -w
use strict;

# [[:blank:]]
my @tableau = ( "ça va?", "tab  ulation","tabulation"," vu!");
print 'regex : if ( $texte =~ m/[[:blank:]]/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/[[:blank:]]/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

caractère d'espacement quelconque : \s et [[:space:]]

modifier

On cherche ici un espacement tel qu'une tabulation, un espace, un saut de ligne ou de page. Pour chercher un caractère qui n'est pas un espacement : \S.

#!/usr/bin/perl -w
use strict;

# \S  = non [[:blank:]]
my @tableau = ( "   ", " ","t   "," v");
print 'regex : if ( $texte =~ m/\S/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/\S/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

lettre en minuscule : [[:lower:]]

modifier
#!/usr/bin/perl -w
use strict;

# [[:lower:]]
my @tableau = qw( ABCDE ABCdE);
print 'regex : if ( $texte =~ m/[[:lower:]]/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/[[:lower:]]/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

lettre en majuscule : [[:upper:]]

modifier
#!/usr/bin/perl -w
use strict;

# [[:upper:]]
my @tableau = qw( abcde abcDe);
print 'regex : if ( $texte =~ m/[[:upper:]]/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/[[:upper:]]/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

caractère de ponctuation : [[:punct:]]

modifier

correspondances partielles : []

modifier

Nous cherchons une correspondance avec uniquement une partie des caractères, nous utilisons alors []. Nous pouvons choisir d'énumérer explicitement tous les caractères dont nous souhaitons vérifier la correspondance en les écrivant entre ces crochets. Un moyen plus répandu (lorsque cela est possible) est d'utiliser une suite de caractères, par exemple de « a » à « z » de cette manière : [a-z]. Pour correspondre avec l'inverse de ce qu'on note entre crochets, il suffit de mettre en première lettre « ^ ». Ainsi, la recherche inverse de l'exemple précédent devient : [^a-z].

exemple de correspondance partielle
modifier

Cherchons à faire correspondre uniquement les caractères de « a » à « e » en minuscule, de « G » à « L » en majuscule et les chiffres de « 0 » à « 5 » et les chiffres 7 et 9.

$texte =~ /[a-eG-K0-579]/;

crochets []

#!/usr/bin/perl -w
use strict;
# crochets []
@tableau = qw(affiche affole affuble affriole effectué effacer);
print 'Test sur la regex : if ( $texte =~ m/ff[a-df-np-s]/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/ff[a-df-np-s]/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

crochets [^…]

#!/usr/bin/perl -w
use strict;

# crochets [^…] 
@tableau = qw(affiche affole affuble affriole effectué effacer);
print 'Test sur la regex : if ( $texte =~ m/ff[^ieo]/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/ff[^ieo]/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

Options

modifier

ignorer la casse

modifier

La « casse » désigne en informatique la différence entre minuscule et majuscule. Si on choisi d'ignorer la casse, alors nous recherchons un motif sans distinction avec ou sans majuscules. Pour cela nous avons l'option i.

recherche en mode multi-ligne

modifier

Nous recherchons un motif en traitant la chaîne en tant que constituée de plusieurs lignes. Concrètement, nous voulons que ^ et $ ne correspondent pas seulement au début et fin de la chaîne, mais aussi au début et fin de chaque ligne. Pour cela, nous avons l'option m.

exemple de recherche en mode multi-ligne

modifier
my $texte = "Ce texte
est sur
plusieurs lignes";

# utilisation de l'option m dans une recherche (fonctionnalité de recherche m)
if( $texte =~ m/sur$/m ) 
{
	say "On a trouvé « sur » en fin de ligne";
}
else
{
	say "On n'a pas trouvé « sur » en fin de ligne";
}

faire correspondre . à un saut de ligne

modifier

Lorsque nous faisons des recherches sur plusieurs lignes, on souhaiterait pouvoir considérer le saut de ligne comme un caractère quelconque (comme tous les autres). Nous pouvons faire cela avec l'option s.

exemple utilisation de l'option s

modifier

Nous reprenons l'exemple précédent avec la recherche sur plusieurs lignes, désormais nous n'avons pas à écrire explicitement le saut de ligne, nous pouvons utiliser le métacaractère ..

my $texte = "Ce texte
est sur
plusieurs lignes";

if( $texte =~ m/sur.plusieurs/s ) 
{
	say "On a trouvé « sur plusieurs » dans le texte";
}
else
{
	say "On n'a pas trouvé « sur plusieurs »";
}

permettre d'écrire l'expression régulière avec des commentaires

modifier

L'option « x » permet d'écrire une expression régulière sans tenir compte des caractères d'espacement. Ainsi, on peut espacer ses expressions avec des retours à la ligne si on veut, sans que cela n'influe sur votre expression. Si vous souhaitez rechercher une chaîne de caractères avec des espaces à certains endroits, il faudra l'indiquer explicitement dans votre expression avec la recherche d'un caractère d'espacement (voir plus haut).

appliquer un changement plusieurs fois dans la chaîne

modifier

Lors de la substitution de texte, nous souhaiterions par exemple changer tous les caractères « a » par « b ». Par défaut, seule la première occurrence de « a » serait modifiée. Pour changer ce comportement et changer tous les « a », nous utilisons l'option g.

#!/usr/bin/perl -w
use strict;
# remplacement simple
my $texte = "affuble mutin hure mure fumé luit";
    print $texte."\n";
    $texte =~ s/u/a/;
    print $texte;
print "\n" x 2;

# remplacement multiple
$texte = "affuble mutin hure mure fumé luit";
    print $texte."\n";
    $texte =~ s/u/a/g ;
    print $texte;
print "\n";

cumuler les options

modifier

Nous pouvons additionner les options en les écrivant à la suite (/ig par exemple). Nous avons déjà vu un exemple plus haut en cumulant les options m (recherche de motif sur plusieurs lignes) et s (retour chariot inclut dans les caractères auxquels le point correspond). Autre exemple, nous pouvons effectuer une substitution plusieurs fois par ligne en ignorant la casse du motif à remplacer.

Les ancres correspondent au placement du texte à rechercher.

rechercher en début de chaîne : ^ et \A

modifier

On utilise pour cela le caractère ^ ou \A qui correspond au début de la chaîne. Avec l'option /m le caractère ^ correspond également à tout début de ligne (voir plus haut).

exemple de recherche en début de chaîne

modifier
my $texte = "Hello world!";
$texte =~ /world/ ; # correspond
$texte =~ /^world/ ; # ne correspond pas car « world » n'est pas au début de la chaîne

^ début de ligne

#!/usr/bin/perl -w
use strict;
# ^ début de ligne 
@tableau = ("affiche", "âme", "à toi", " affiche");
print 'regex : if ( $texte =~ m/^a/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/^a/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "(espace en-tête)";
print "\n";

rechercher en fin de chaîne : $, \Z et \z

modifier

$ est l'analogue de ^ mais en fin de chaîne (ou de ligne aussi avec option m). \z est l'analogue de A en fin de chaîne, donc non affecté par l'option m. \Z est comme \z mais peut correspondre aussi à un retour à la ligne en fin de chaîne.

exemple de recherche en fin de chaîne

modifier
my $texte = "Hello world!";
$texte =~ /Hello/ ; # correspond
$texte =~ /Hello$/ ; # ne correspond pas car « Hello » n'est pas en fin de chaîne

$ fin de ligne

#!/usr/bin/perl -w
use strict;

# $ fin de ligne 
@tableau = ("affiche", "affiche.", "affiches");
print 'regex : if ( $texte =~ m/che.$/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/che.$/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

Quantification

modifier

Nous allons aborder maintenant la partie concernant l'identification d'un motif se répétant.

exemple d'introduction au quantifications

modifier

Avant de commencer, il faut se confronter au problème. Nous avons une chaîne de caractères, et nous souhaitons savoir si elle contient deux fois le caractère « a ». Vu l'étendue des connaissances acquises jusque-là, nous devrions résoudre le problème comme suit :

$texte =~ /aa/; # si $texte contient 2 fois le caractère « a » il correspondra

Maintenant, on souhaite savoir si la chaîne de caractères contient deux fois le caractère « a » (jusque-là, le problème reste identique) sauf que ces deux caractères peuvent être séparés par un caractère quelconque (ou pas). Là encore, vu l'étendue des connaissances acquises actuellement, cela donnerait :

if( $texte =~ /aa/ || $texte =~ /a.a/) # pas de caractère séparateur ou un seul
{
	... # si ça correspond, on fait quelque chose
}

Nous résolvons le problème ici assez facilement, mais qu'en est-il si nous cherchons deux fois le caractère « a » avec un nombre quelconque de caractères séparateurs ? Impossible actuellement.

Quantifier par un nombre : {}

modifier

Nous avons une chaîne de caractères, et nous recherchons un motif quelconque se répétant un certain nombre de fois. Nous pouvons expliciter ce nombre de fois, ou simplement donner une tranche :

 * de x à y fois : {x,y}
 * au moins x fois : {x,}

exemples avec : {}

modifier
my $texte = "coucou!";
$texte =~ /c.{2}c/ ; # correspond
$texte =~ /c.{1,3}c/ ; # correspond
$texte =~ /cou{2}/ ; # ne correspond pas !
$texte =~ /c.{1,}c/ ; # correspond

Ligne 2 : il y a correspondance car nous avons précisément deux caractères séparant les deux lettres « c ». Ligne 3 : il y a correspondance là aussi, car il y a un nombre compris entre 1 et 3 caractères entre les deux lettres « c ». Attention : ligne 4 ne correspond pas car il faudrait qu'il y ait 2 lettres « u » consécutives et non toute la chaîne « cou » ! Nous verrons plus tard comment procéder pour faire cela. Enfin, ligne 5 correspond car on cherche « au moins » un caractère entre deux « c ».

Entre 0 ou 1 occurrence : ?

modifier

Il est possible que le caractère (ou plus généralement le motif) soit présent ou non. On utilise pour cela le quantificateur ?.

#!/usr/bin/perl -w
use strict;

# quantificateur ?
@tableau = qw(pommes pomme mess);
print 'Test sur la regex : if ( $texte =~ m/me[s]?$/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/me[s]?$/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

Entre 0 ou un nombre non déterminé d'occurrences : *

modifier

Quand il peut y avoir 0 ou une infinité d'occurrences d'un motif, on utilise le caractère spécial * .

#!/usr/bin/perl -w
use strict;

# *
@tableau = qw(miss mess messe messes);
print 'Test sur la regex : if ( $texte =~ m/e[s]*$/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/e[s]*$/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n" x 2;

# +
@tableau = qw(pomme pommes mess messe messes);
print 'Test sur la regex : if ( $texte =~ m/e[s]+$/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/e[s]+$/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

Capturer du texte

modifier

La capture du texte (d'un certain motif) permet de réutiliser ce texte pour le replacer (en le modifiant éventuellement).

Simple capture de texte : ()

modifier

Nous souhaitons prendre une partie du texte pour le réutiliser. Lorsque vous capturez du texte, les variables $1, $2 … sont mises à jour avec la capture que vous venez de faire entre parenthèses. Ainsi, le premier groupe capturé par () pourra être réutilisé dans votre code via la variable $1, le second avec la variable $2 et ainsi de suite jusqu'à $9.

#!/usr/bin/perl -w
use strict;

# groupe de capture () avec antéréférence
@tableau = qw(dare-dare borborigme calcul barbare Tartare bébé );
print 'Test sur la regex : if ( $texte =~ m/(...)\1/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/(...)\1/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "(é = 2 octets)";
print "\n";

Ne pas capturer le motif : (?:)

modifier
my $texte = "J'aime le fromage";
$texte =~ m/(?:aime).*(fromage)/;
say $1; # affiche "fromage"
#!/usr/bin/perl -w
use strict;

# non capture des 6 radicaux, réutilisation de 12 motifs sujet-suffixe
my $texte = "je chante, tu chantes, il chante, nous chantons, vous chantez, ils chantent";
    print $texte."\n";
    $texte =~ s/(\w+) (?:\w+)(\w), (\w+) (?:\w+)(\w{2}), (\w+) (?:\w+)(\w), (\w+) (?:\w+)(\w{3}), (\w+) (?:\w+)(\w{2}), (\w+) (?:\w+)(\w{3})/$1_$2 $3_$4 $5_$6 $7_$8 $9_$10 $11_$12/;
    print $texte."\n";
print "\n" x 2;

Réutiliser les motifs capturés : \1 \2 …

modifier
my $texte = "J'aime le le fromage"; # doublon «le»
$texte =~ s/(\w+) \1/$1/g;
# $texte contient "J'aime le fromage" sans doublon.
# non capture du 3e, antéréférence au 6e, réutilisation de 8 motifs
my $texte = "chanter - verbe du 1e groupe - e es e ons ez ent";
    print $texte."\n";
    $texte =~ s/(\w+)(..) (?:-.*-) (\w+) (\w+) \3 (\w+) (\w+) (\w+)/je $1$3, tu $1$4, il $1$3, nous $1$5, vous $1$6, ils $1$7./;
    print $texte."\n";
print "\n";

Définir des alternatives : (a|b)

modifier
#!/usr/bin/perl -w
use strict;

# "ou" inclusif |
my @tableau = qw(satin malin matin calin);
print 'regex : if ( $texte =~ m/m|t/ )'."\n";
foreach my $texte (@tableau){
    print "“$texte“";
    if ( $texte =~ m/m|t/ ){ print " vrai, "; }
    else{ print " faux, "; }
}
print "\n";

Captures avides ou non avides

modifier

Le plus simple pour comprendre ce qu'est une capture avide est de prendre un exemple.

capture avide

modifier
exemple 1 de capture avide
modifier

Soit la capture « (c+) » qui va prendre une fois ou un nombre non limité de fois la lettre « c ».

$texte = "cccccc";
$texte =~ /^(c+)/;
say $1;

Si vous exécutez ce code, vous verrez affiché « cccccc » et non pas un seul « c ». Cela est dû à l'avidité (en anglais greedy) des opérateurs.

exemple 2 de capture avide
modifier

Un autre exemple, un peu plus complexe, pour bien comprendre le phénomène, qui peut s'avérer gênant : nous avons une chaîne de caractères avec des champs délimités par des doubles points « : » :

$texte = "nom:prenom:age:date/de/naissance";
$texte =~ /^(.+):/; # .+ : on récupère le maximum de caractères avant de tomber sur un double points
say $1;

Ici, nous affichons le nom, le prénom et l'âge, car nous prenons la chaîne la plus longue possible correspondant à notre expression. C'est à dire, la chaîne la plus longue se terminant par un double point.

capture non avide

modifier

Maintenant, ce qu'on voudrait c'est de ne prendre que la plus petite partie possible qui correspond à notre expression régulière. La solution est de remplacer chaque quantificateur par sa version non avide :

  • , +, ?, et {} deviennent respectivement *?, +?, ?? et {}?.
#!/usr/bin/perl -w
use strict;
# + (ou *) avide
my $texte = "anticonstitutionnellement";
$texte =~ /^(.+)i/;
print $1."\n"; # anticonstitut

# +? (ou *?) sobre
$texte =~ /^(.+?)i/;
print $1."\n"; # ant

# {…} avide
$texte =~ /^(.{6,})e/;
print $1."\n"; # anticonstitutionnellem
# {…}? sobre
$texte =~ /^(.{6,}?)e/;
print $1."\n"; # anticonstitutionn

Les coulisses des regex

modifier

@+ et @- les @rchiveurs de regex

modifier

@+ matrice des fins des chaînes trouvées ($+[0] fin globale, $+[1] fin de $1 …)

@- matrice des débuts des chaînes

Les items de @+ ne se notent pas @+[0], @+[1] … mais $+[0], $+[1] … car ce sont des scalaires.

#!/usr/bin/perl -w
use strict;

my $texte = "anticonstitutionnellement";
$texte =~ m/(cons....).*(nne).*(ll)/;

# les 3 correspondances (matchings) trouvées
print "$1  $2  $3\n" ; # constitu  nne  ll

# traces de la dernière recherche (double "l")
print substr($texte,0,$-[0])."  ".substr($texte,$-[0],$+[0]-$-[0])."  ".substr($texte, $+[0])."\n";

# print substr($texte,0,4)        substr($texte,4,16)                   substr($texte, 20)
#       anti                      constitutionnell                      ement
#       avant                     chaîne trouvée                        après

# variable $2
print $-[2]." ".$+[2]."\n"; # 15 18 : début & fin de "nne"

# @+ matrice des fins des chaînes $0 $1 $2 $3
print "@+\n"; # 20 12 18 20

# @- matrice des débuts des chaînes
print "@-\n"; # 4 4 15 18
  1. https://unicode-table.com/fr/
  2. https://docstore.mik.ua/orelly/webprog/pcook/ch13_05.htm
  3. https://www.regular-expressions.info/posixbrackets.html
  4. https://www.regular-expressions.info/unicode.html
  5. https://www.regextester.com/15
  6. Jan Goyvaerts, Steven Levithan, Regular Expressions Cookbook, O'Reilly Media, Inc., (lire en ligne)
  7. Les options sont appelées modificateurs (modifiers en anglais), voir https://www.regular-expressions.info/modifiers.html