Perl moderne : 2014

Prenez le contrôle de votre programmation avec le Perl moderne.


précédentsommairesuivant

VII. Expressions régulières et correspondance

Une grande partie de la puissance de traitement des données textuelles de Perl vient de son utilisation des expressions régulières. Une expression régulière (9) (également nommée regex ou regexp) est un motif qui décrit les caractéristiques d'un morceau de texte. Un moteur d'expressions régulières applique ces motifs pour reconnaître des parties de texte ou pour les remplacer.

La maîtrise des expressions régulières est une quête ardue, mais une connaissance même assez sommaire des regex vous donnera une grande puissance expressive. La documentation standard sur les regex de Perl comprend un tutoriel (perldoc perlretut), un guide de référence (perldoc perlreref), et une documentation complète (perldoc perlre). Le livre Image non disponibleMastering Regular Expressions de Jeffrey Friedl explique la théorie et la mécanique de la façon dont les expressions régulières fonctionnent.

VII-A. Variables littérales

Une expression régulière peut être une simple sous-chaîne :

 
Sélectionnez
my $lieu = 'Sambreville';
say 'Trouvé de l\'ambre !' if $lieu =~ /ambre/;

L'opérateur de correspondance ou de reconnaissance (m//, abrégé //) identifie dans cet exemple une expression régulière, ambre. Ce motif n'est pas un mot. Il signifie « le caractère a, suivi par le caractère m, suivi par le caractère b, suivi par un r, puis par un e ». Chaque caractère dans le motif est un élément indivisible (un atome). Un atome correspond ou pas.

L'opérateur de liaison des regex (=~) est un opérateur infixé (FixitéFixité) qui applique l'expression régulière de son second opérande à une chaîne comme premier opérande. Dans un contexte scalaire, une correspondance est vraie si elle réussit. La forme négative de l'opérateur de liaison (!~) retourne vrai sauf si la correspondance réussit.

N'oubliez pas index !

La fonction index peut également rechercher une sous-chaîne littérale dans une chaîne (ainsi que rindex pour une recherche depuis la fin de la chaîne). L'utilisation d'un moteur de regex pour cela, c'est comme envoyer un drone de combat autonome au magasin du coin pour acheter du fromage — mais Perl vous permet d'écrire le code qui vous semble le plus clair.

L'opérateur de substitution, s///, est une sorte d'opérateur circumfixé (FixitéFixité) avec deux opérandes. Lorsqu'il est utilisé avec l'opérateur de liaison des regex, son premier opérande (la partie entre le premier et le deuxième séparateur) est une expression régulière à comparer. Lorsqu'il est utilisé avec l'opérateur de liaison, le second opérande (la partie entre le deuxième et le troisième séparateur) est une sous-chaîne utilisée pour remplacer toutes les occurrences du premier opérande trouvées dans la chaîne initiale. Par exemple, pour guérir les allergies d'été gênantes :

 
Sélectionnez
my $etat = 'Je me sens mal.';
$etat    =~ s/mal/bien/;
say $etat;

VII-B. L'opérateur qr// et les combinaisons regex

L'opérateur qr// crée des expressions régulières de première classe. Utilisez une regex construite avec lui comme opérande de l'opérateur de correspondance ou comme premier opérande de l'opérateur de substitution :

 
Sélectionnez
my $nom = 'Sambreville';
my $ambre = qr/ambre/;
say 'Trouvé de l\'ambre !' if $nom =~ /$ambre/;

... ou combinez plusieurs objets regex en motifs complexes :

 
Sélectionnez
my $nom = 'Sambreville';
my $ambre = qr/ambre/;
my $ville = qr/ville/;

say 'Trouvé ambre en ville !'
    if $nom =~ /$ambre$ville/;

like( $nom, qr/$ambre$ville/,
               'Trouvé ambre en ville !' );

Comme is , avec like en plus

La fonction like du module Test::More teste si le premier argument correspond à l'expression régulière fournie comme second argument.

VII-C. Quantificateurs

Les expressions régulières deviennent plus puissantes grâce à l'utilisation des quantificateurs de regex. Ces métacaractères gèrent le nombre de fois où un composant de regex peut apparaître dans une chaîne correspondante. Le plus simple est ?, le quantificateur zéro ou un :

 
Sélectionnez
my $chat_ou_cat = qr/ch?at/;

like( 'chat', $chat_ou_cat, "'chat' reconnaît /ch?at/" );
like( 'cat',  $chat_ou_cat, "'cat' reconnaît /ch?at/"  );

Un atome quelconque d'une expression régulière suivi par le caractère ? signifie « correspond à zéro ou un de cet atome ». Cette expression régulière retourne la valeur vraie si zéro ou un caractère h suivent immédiatement le caractère c et précède immédiatement les caractères at. Cette regex reconnaît les deux sous-chaînes littérales chat et cat.

Le quantificateur un ou plusieurs, +, fait correspondre au moins l'un des atomes quantifiés :

 
Sélectionnez
my $quelques_a = qr/cha+t/;

like( 'chat',    $quelques_a, "'chat' reconnaît /cha+t/" );
like( 'chaat',   $quelques_a, "'chaat' correspond"       );
like( 'chaaat',  $quelques_a, "'chaaat' correspond"      );
like( 'chaaaat', $quelques_a, "'chaaaat' correspond"     );

unlike( 'cht',   $quelques_a, "'cht' ne correspond pas"  );

Il n'y a pas de limite théorique au nombre maximum d'atomes quantifiés qui peuvent correspondre.

Le quantificateur zéro, un ou plusieurs, *, cherche à reconnaître zéro, une ou plusieurs instances de l'atome quantifié :

 
Sélectionnez
my $tout_a = qr/cha*t/;

like( 'chat',    $tout_a, "'chat' reconnaît /cha*t/" );
like( 'chaat',   $tout_a, "'chaat' correspond"       );
like( 'chaaat',  $tout_a, "'chaaat' correspond"      );
like( 'chaaaat', $tout_a, "'chaaaat' correspond"     );
like( 'cht',     $tout_a, "'cht' correspond"         );

Cela peut paraître ridicule, mais ça vous permet de spécifier les composants optionnels d'une expression régulière. Utilisez-le pourtant avec parcimonie : c'est un outil brutal et coûteux. La plupart des expressions régulières bénéficient de l'utilisation des quantificateurs ? et + beaucoup plus que de l'utilisation de *. Préciser l'intention améliore souvent la clarté.

Les quantificateurs numériques expriment le nombre de fois où un atome peut correspondre. {n} signifie qu'une correspondance doit avoir lieu exactement n fois.

 
Sélectionnez
# équivalent à qr/chat/;
my $un_seul_a = qr/cha{1}t/;

like( 'chat', $un_seul_a, "'chat' reconnaît /cha{1}t/" );

{n,} recherche la correspondance d'un atome au moins n fois :

 
Sélectionnez
# équivalent à qr/cha+t/;
my $quelques_a = qr/cha{1,}t/;

like( 'chat',    $quelques_a, "'chat' correspond à /cha{1,}t/" );
like( 'chaat',   $quelques_a, "'chaat' correspond"             );
like( 'chaaat',  $quelques_a, "'chaaat' correspond"            );
like( 'chaaaat', $quelques_a, "'chaaaat' correspond"           );

{n,m} signifie qu'une correspondance doit être trouvée au moins n fois et pas plus de m fois :

 
Sélectionnez
my $certains_a = qr/cha{1,3}t/;

like( 'chat',    $certains_a, "'chat' correspond à /cha{1,3}t/" );
like( 'chaat',   $certains_a, "'chaat' correspond"              );
like( 'chaaat',  $certains_a, "'chaaat' correspond"             );

unlike( 'chaaaat', $certains_a, "'chaaaat' ne correspond pas"   );

Vous pouvez exprimer les quantificateurs symboliques (?, + et *) sous la forme de quantificateurs numériques, mais les quantificateurs symboliques sont plus courts et sont utilisés plus souvent. Par exemple, l'expression régulière /a?/ est synonyme de /a{0,1}/, de même que /b+/ est synonyme de /b{1,}/ et que l'expression /c*/ est équivalente à /c{0,}/, mais c'est généralement la première forme qui sera préférée dans chaque cas.

VII-D. Gourmandise

Les quantificateurs + et * sont gourmands : ils essaient de trouver dans la chaîne d'entrée autant de correspondances que possible. C'est particulièrement pernicieux. Examinez une utilisation naïve du motif « zéro ou plusieurs caractères non-saut de ligne » de .* :

 
Sélectionnez
# une regex faillible
my $ciel_bleu = qr/ciel.*bleu/;

say 'Il fait beau !'
    if 'Le ciel est bleu' =~ $ciel_bleu;

say 'Il fait beau !'
    if 'logiciel, cordon-bleu!' =~ $ciel_bleu;

Les quantificateurs gourmands commencent par tout reconnaître au début. Si cela ne réussit pas, le moteur de regex reculera d'un caractère à la fois jusqu'à ce qu'il trouve une correspondance (ou finisse par échouer).

Le modificateur de quantificateur ? transforme un quantificateur gourmand en non gourmand :

 
Sélectionnez
my $gourmandise_minimale = qr/ciel.*?bleu/;

Avec un quantificateur non gourmand, le moteur d'expression régulière préférera la correspondance la plus courte possible. Si cette correspondance échoue, le moteur va augmenter d'un caractère à la fois le nombre de caractères identifiés par la combinaison .*?. Comme * recherche une correspondance de zéro à plusieurs fois, la correspondance potentielle minimale pour cette combinaison est de zéro caractère :

 
Sélectionnez
say 'Il fait beau'
if 'beaucielbleu' =~ /$gourmandise_minimale/;

Utilisez +? pour rechercher la correspondance d'une ou plusieurs sous-chaînes de façon non gourmande :

 
Sélectionnez
my $gourmandise_minimale_plus = qr/ciel.+?bleu/;

unlike( 'beaucielbleu',  $gourmandise_minimale_plus );

like( 'beau ciel bleu',  $gourmandise_minimale_plus );

Le modificateur de quantificateur ? s'applique aussi au quantificateur ? (zéro ou une correspondance) ainsi qu'aux quantificateurs d'intervalle. Il provoque toujours la correspondance minimale entre l'expression régulière et la chaîne en entrée.

Les regex sont puissantes, mais elles ne sont pas toujours la meilleure façon de résoudre un problème. C'est doublement vrai pour les motifs gourmands .+ et .*. Un amateur de mots croisés qui doit remplir un mot de cinq lettres, commençant par h et finissant par s, pour la définition« sol riche ») trouvera beaucoup trop de candidats non valides pour le motif :

 
Sélectionnez
my $sept_vertical = qr/h$uniquement_lettres*s/;

Si la recherche est exécutée parmi tous les mots d'un dictionnaire, les correspondances antihéros, Bahamas et Chartres seront trouvées longtemps avant d'atteindre la réponse correcte humus. Non seulement ces mots sont trop longs, mais les correspondances commencent au milieu des mots.

VII-E. Ancres de regex

Il est important de savoir comment le moteur d'expressions régulières gère les correspondances gourmandes, mais c'est tout aussi important de savoir quel genre de correspondances vous voulez et vous ne voulez pas. Les ancres de regex forcent le moteur de regex à commencer ou finir une correspondance à une position fixe. L'ancre de début de chaîne (\A) stipule que toute correspondance doit commencer dès le début de la chaîne :

 
Sélectionnez
# correspond aussi à "haïssable", "hexose" et "huissier"
my $sept_vertical = qr/\Ah${uniquement_lettres}{3}s/;

L'ancre de fin de chaîne (\z) exige la fin de la correspondance exactement à la fin de la chaîne.

 
Sélectionnez
# correspond aussi à "haras", mais une amélioration évidente
my $sept_vertical = qr/\Ah${letters_only}{3}s\z/;

Vous verrez souvent les assertions ^ et $ utilisées pour trouver des correspondances au début et à la fin des chaînes. ^ trouve une correspondance au début de la chaîne, mais dans certaines circonstances avec des chaînes multilignes, il peut trouver une correspondance dans la chaîne juste après un saut de ligne. De même, $ trouve une correspondance à la fin de la chaîne (juste avant un saut de ligne, s'il existe), mais elle peut trouver une correspondance juste avant un saut de ligne au milieu de la chaîne. \A et \z sont plus spécifiques et, par conséquent, plus utiles.

L'ancre de début ou fin de mot (\b) correspond uniquement à la limite entre un caractère de mot (\w) et un caractère de non-mot (\W). Cette délimitation n'est pas un caractère en soi ; elle n'a pas de largeur. Elle est invisible. Utilisez une regex ancrée pour trouver humus en interdisant Bahamas :

 
Sélectionnez
my $sept_vertical = qr/\bh${uniquement_lettres}{3}s\b/;

VII-F. Métacaractères

Perl interprète plusieurs caractères dans des expressions régulières comme des métacaractères, des caractères représentant autre chose que leur interprétation littérale. Vous avez vu déjà quelques-uns de ces métacaractères (\b, . et ?, par exemple). Les métacaractères donnent aux utilisateurs des regex une puissance bien au-delà de simples correspondances de sous-chaînes. Le moteur de regex traite tous les métacaractères comme atomes.

Le métacaractère . signifie « correspond à tout caractère sauf un saut de ligne ». Beaucoup de débutants oublient cette nuance. Une recherche de regex simple — ignorant l'amélioration évidente de l'utilisation d'ancres — pour notre problème de mots croisés pourrait être /h...s/. Bien sûr, il y a toujours plus d'une façon d'obtenir la bonne réponse :

 
Sélectionnez
for my $mot (@mots)
{
    next unless length( $mot ) == 5;
    next unless $mot =~ /h...s;
    say "Possibilité : $mot";
}

Si les correspondances possibles dans @mots sont plus que de simples mots, vous obtiendrez de faux positifs. Le métacaractère point . reconnaît aussi des caractères de ponctuation, des espaces et des nombres. Soyez précis ! Le métacaractère \w représente tous les caractères alphanumériques (Unicode et chaînes de caractèresUnicode et chaînes de caractères) et le trait de soulignement :

 
Sélectionnez
next unless $mot =~ /h\w\ws/;

Le métacaractère \d recherche des correspondances des chiffres (aussi dans le sens Unicode) :

 
Sélectionnez
# un comparateur non robuste de numéros de téléphone
next unless $numero =~ /\d{3}-\d{3}-\d{4}/;
say "Votre numéro : $numero";

Utilisez le métacaractère \s pour rechercher la correspondance d'un espace. L'espace signifie un espace littéral, un caractère de tabulation, un retour de chariot, un saut de page ou un saut de ligne :

 
Sélectionnez
my $deux_mots_de_trois_lettres = qr/\w{3}\s\w{3}/;

Négations des métacaractères

Ces métacaractères ont des formes négatives. Utilisez \W pour trouver la correspondance de n'importe quel caractère sauf un caractère alphabétique. \D permet de rechercher des caractères non numériques. Utilisez \S pour rechercher la correspondance d'autre chose qu'un espace. Utilisez \B pour rechercher une correspondance n'importe où, sauf à une limite de mot.

VII-G. Classes de caractères

Si aucun de ces métacaractères n'est assez spécifique, vous pouvez créer vos propres groupes de caractères en classes de caractères en les plaçant entre crochets. Une classe de caractères vous permet de traiter un groupe d'options comme un seul atome.

 
Sélectionnez
my $voyelles_ascii = qr/[aeiouy]/;
my $possible_chat  = qr/ch${voyelles_ascii}t/;

L'interpolation arrive

Sans ces accolades, l'analyseur de Perl interpréterait le nom de la variable comme $voyelles_asciit, ce qui soit provoque une erreur de compilation au sujet d'une variable inconnue, soit interpole le contenu d'une $voyelles_asciit existante dans l'expression rationnelle.

Le tiret (-) vous permet d'inclure une plage contiguë de caractères dans une classe, comme cette regex $uniquement_lettres_ascii :

 
Sélectionnez
my $uniquement_lettres_ascii = qr/[a-zA-Z]/;

Pour inclure le tiret comme membre de la classe, utilisez-le au début ou à la fin de la classe :

 
Sélectionnez
my $ponctuation_interessante = qr/[-!?]/;

... ou protégez-le par une barre oblique inverse (ou antislash) :

 
Sélectionnez
my $caracteres_ligne = qr/[|=\-_]/;

L'utilisation de l'accent circonflexe (^) comme premier élément de la classe de caractères signifie « toute autre chose à l'exception de ces caractères » :

 
Sélectionnez
my $pas_une_voyelle_ascii = qr/[^aeiouy]/;

Utilisez un accent circonflexe n'importe où, sauf à la première position pour en faire un membre de la classe de caractères. Pour inclure un tiret dans une classe de caractères niée, soit placez-le après l'accent circonflexe ou à la fin de la classe, soit protégez-le par un antislash.

VII-H. Capturer

Les expressions régulières vous permettent de grouper et de capturer des portions de correspondance pour une utilisation ultérieure. Pour extraire un numéro américain de téléphone de la forme (202) 456-1111 à partir d'une chaîne :

 
Sélectionnez
my $code_zone        = qr/\(\d{3}\)/;
my $numero_local     = qr/\d{3}-?\d{4}/;
my $numero_telephone = qr/$code_zone\s?$numero_local/;

Remarquez en particulier l'échappement des parenthèses dans $code_zone. Les parenthèses sont spéciales dans les expressions régulières Perl. Elles groupent des atomes en unités plus grandes et en même temps capturent des portions de correspondance de chaînes. Pour rechercher des correspondances des parenthèses littérales, protégez-les avec des antislash comme dans $code_zone.

VII-H-1. Captures nommées

Perl 5.10 a ajouté les captures nommées, qui vous permettent de capturer des portions de correspondance en appliquant une expression régulière et d'y accéder plus tard. Par exemple, lors de l'extraction d'un numéro de téléphone à partir des informations de contact :

 
Sélectionnez
if ($contact_info =~ /(?<telephone>$numero_telephone)/)
{
    say "Trouvé un numéro $+{telephone}";
}

Les regex ont tendance à ressembler à une soupe à la ponctuation jusqu'au moment où vous pouvez regrouper les diverses parties en morceaux. La syntaxe de capture nommée présente la forme :

 
Sélectionnez
(?<nom_capture> ... )

Des parenthèses entourent la capture. La construction ?<nom> suit immédiatement la parenthèse ouverte et fournit un nom pour cette capture spécifique. Le reste de la capture est une expression régulière.

Lorsqu'une correspondance au motif inclus réussit, Perl met à jour la variable magique %+. Dans ce hachage, la clé est le nom de la capture et la valeur est la portion de la chaîne correspondant à la capture.

VII-H-2. Captures numérotées

Perl met en œuvre depuis très longtemps des captures numérotées :

 
Sélectionnez
if ($contact_info =~ /($numero_telephone)/)
{
    say "Trouvé un numéro $1";
}

Cette forme de capture ne fournit aucun nom d'identification et ne fait rien à %+. Au lieu de cela, Perl stocke ici la sous-chaîne capturée dans une série de variables magiques. La première capture correspondante trouvée par Perl va dans $1, la deuxième dans $2 et ainsi de suite. Le comptage des captures commence à la parenthèse ouvrante de la capture. Ainsi, la première parenthèse gauche commence la capture dans $1, la seconde dans $2, et ainsi de suite.

La syntaxe pour les captures nommées est plus longue que pour les captures numérotées, mais elle offre plus de clarté. Compter les parenthèses gauches est un travail fastidieux et combiner les expressions régulières qui contiennent des captures numérotées est difficile. Les captures nommées améliorent la maintenabilité des regex — bien que les collisions de noms soient possibles, elles sont relativement rares. Minimisez les risques en utilisant les captures nommées uniquement dans les expressions régulières finales, mais pas dans les « briques » servant à assembler des expressions régulières plus complexes.

Dans un contexte de liste, une correspondance de regex retourne une liste de sous-chaînes capturées :

 
Sélectionnez
if (my ($numero) = $contact_info =~ /($numero_telephone)/)
{
    say "Trouvé un numéro $numero";
}

Les captures numérotées sont également utiles lors des substitutions simples, où les captures nommées peuvent être plus verbeuses :

 
Sélectionnez
my $commande = 'Des brownies Végan !';

$commande =~ s/Végan (\w+)/Végétarien $1/;
# ou
$commande =~ s/Végan (?<aliment>\w+)/Végétarien $+{aliment}/;

VII-I. Regroupement et alternatives

Les exemples précédents ont tous appliqué des quantificateurs à des atomes simples. Vous pouvez les appliquer à n'importe quel élément d'une regex :

 
Sélectionnez
my $porc     = qr/porc/;
my $haricots = qr/haricots/;

like( 'porc aux haricots', qr/\A$porc?.*?$haricots/,
      'peut-être porc, certainement haricots' );

Si vous étendez manuellement l'expression régulière, les résultats peuvent vous surprendre :

 
Sélectionnez
my $porc_aux_haricots = qr/\Aporc?.*haricots/;

like( 'porc aux haricots', qr/$porc_aux_haricots/,
      'peut-être porc, certainement haricots'     );
like( 'por aux haricots', qr/$porc_aux_haricots/,
      'attendez... pas d\'acide ascorbique ici !' );

La spécificité permet parfois de préciser le motif :

 
Sélectionnez
my $porc     = qr/porc/;
my $aux      = qr/aux/;
my $haricots = qr/haricots/;

like( 'porc aux haricots', qr/\A$porc? $aux? $haricots/,
      'peut-être porc, peut-être aux, certainement haricots' );

Certaines expressions régulières doivent correspondre soit à une chose, soit à l'autre. Le métacaractère d'alternative (|) indique qu'une des deux possibilités peut correspondre.

 
Sélectionnez
my $riz      = qr/riz/;
my $haricots = qr/haricots/;

like( 'riz',      qr/$riz|$haricots/, 'Trouvé riz'      );
like( 'haricots', qr/$riz|$haricots/, 'Trouvé haricots' );

Alors qu'il est facile à interpréter riz|haricots comme ri, suivi soit par z, soit par h, suivi par aricots, les alternatives incluent toujours l'intégralité du fragment jusqu'au délimiteur de regex le plus proche, que ce soit le début ou la fin du motif, une parenthèse fermante, un autre caractère d'alternative ou un crochet.

L'alternative a une priorité inférieure (Ordre des opérations (priorité)Ordre des opérations (priorité)) aux atomes mêmes :

 
Sélectionnez
like(   'riz',      qr/riz|haricots/, 'Trouvé riz'      );
like(   'haricots', qr/riz|haricots/, 'Trouvé haricots' );
unlike( 'rih',      qr/riz|haricots/, 'Trouvé hybride'  );

Pour éviter la confusion, utilisez des fragments nommés dans des variables ($riz|$haricots) ou groupez les candidats d'alternative dans des groupes non capturants :

 
Sélectionnez
my $feculents = qr/(?:pates|patates|riz)/;

La séquence (?:) regroupe une série d'atomes sans faire une capture.

Non-capture pour votre protection

Une expression régulière transformée en chaîne contient un groupe englobant sans capture ; qr/riz/haricots/ devient (?^u:riz|haricots).

VII-J. Autres séquences d'échappement

Pour faire correspondre l'instance littérale d'un métacaractère, préfixez-le avec le caractère d'échappement, l'antislash (\). Vous avez déjà vu cela auparavant, là où \( désigne une seule parenthèse gauche et \] se réfère à un seul crochet fermant. \. désigne un caractère point littéral et non l'atome « faire correspondre tout sauf un caractère explicite de saut de ligne ».

Vous aurez probablement besoin d'échapper le métacaractère d'alternative (|) ainsi que le métacaractère de fin de ligne ($) et les quantificateurs (+, ?, *).

Les caractères de désactivation de métacaractères (\Q et \E) désactivent l'interprétation des métacaractères à l'intérieur de leurs limites. Ceci est particulièrement utile lors de la correspondance du texte provenant d'une source que vous ne contrôlez pas :

 
Sélectionnez
my ($text, $literal_text) = @_;

return $text =~ /\Q$literal_text\E/;

L'argument $literal_text peut contenir n'importe quoi — la chaîne ** ALERTE **, par exemple. Dans le fragment délimité par \Q et \E, Perl va interpréter l'expression régulière comme \*\* ALERTE \*\* et tentera de faire correspondre les caractères astérisque littéraux au lieu de les traiter comme quantificateurs gourmands.

Sécurité des expressions régulières

Soyez prudents lors du traitement des expressions régulières provenant de la saisie de l'utilisateur non fiable. Un maître des regex malveillant pourrait concevoir une expression régulière qui prendrait des années pour trouver des correspondances dans la chaîne de caractères en entrée, créant une attaque par déni de service contre votre programme.

VII-K. Assertions

Les ancres de regex telles que \A, \b, \B et \Z sont une forme d'assertion qui exige que la chaîne satisfasse à une certaine condition. Ces assertions ne correspondent pas à des caractères individuels dans la chaîne. Peu importe ce que la chaîne contient, l'expression régulière qr/\A/ correspondra toujours.

Les assertions de largeur nulle correspondent à un motif. Surtout, elles ne consomment pas la partie du motif qu'elles reconnaissent. Par exemple, pour trouver un seul chat, vous pouvez utiliser une assertion de fin de mot :

 
Sélectionnez
my $juste_un_chat = qr/cat\b/;

… mais si vous voulez trouver un félin non désastreux, vous pourriez utiliser une assertion négative avant de largeur nulle :

 
Sélectionnez
my $felin_prudent = qr/cat(?!astrophe)/;

La construction (?!...) correspond au motif cat uniquement si le motif astrophe ne suit pas immédiatement. L'assertion positive arrière de largeur nulle :

 
Sélectionnez
my $felin_desastreux = qr/cat(?=astrophe)/;

... correspond à l'expression cat uniquement si l'expression astrophe suit immédiatement. Lorsqu'une expression régulière normale peut accomplir la même chose, envisagez une regex pour trouver dans le dictionnaire tous les mots non catastrophiques qui commencent par cat :

 
Sélectionnez
my $felin_desastreux = qr/cat(?!astrophe)/;

while (<$mots>)
{
    chomp;
    next unless /\A(?<cat>$felin_desastreux.*)\Z/;
    say "Trouvé un '$+{cat}' non-catastrophique";
}

L'assertion de largeur nulle ne consomme rien de la chaîne source, laissant le fragment ancré <.*\Z> rechercher la correspondance. Dans le cas contraire, la capture capturerait uniquement la partie cat de la chaîne source.

Pour affirmer que votre félin ne se produit jamais au début d'une ligne, vous pouvez utiliser une assertion négative arrière de largeur nulle. Ces assertions doivent avoir des tailles fixes. Vous ne pouvez pas utiliser les quantificateurs :

 
Sélectionnez
my $milieu_chat = qr/(?<!\A)chat/;

La construction (?<!...) contient le motif de largeur fixe. Vous pouvez également exprimer que cat doit toujours se trouver immédiatement après un caractère d'espace avec une assertion positive arrière de largeur nulle :

 
Sélectionnez
my $espace_chat = qr/(?<=\s)chat/;

La construction (?<=...) contient le motif de largeur fixe. Cette approche peut être utile pour combiner une correspondance globalede regex avec le modificateur \G.

Une nouvelle fonctionnalité des regex Perl est l'assertion keep \K. Cette assertion positive arrière de largeur nulle peut avoir une longueur variable :

 
Sélectionnez
my $espacee_chat = qr/\s+\Kchat/;

like( 'mon chat a été dans l\'espace', $espacee_chat );
like( 'mon  chat  a  été  dans  un  double-espace',
     $espacee_chat );

\K est étonnamment utile pour certaines substitutions qui éliminent la fin d'un motif. Il vous permet de rechercher un motif, mais de supprimer seulement une partie de celui-ci :

 
Sélectionnez
my $exclamation = 'Ceci est un désastre!';
$exclamation    =~ s/dé\K\w+!/./; 
# $exclamation vaut maintenant : 'Ceci est un dé!'

like( $exclamation, qr/\bdé\./,
                       "Pas si mal que ça !" );

Tout jusqu'à l'assertion \K correspond, mais seulement la partie qui suit l'assertion est substituée.

VII-L. Modificateurs de regex

Plusieurs modificateurs changent le comportement des opérateurs d'expression régulière. Ces modificateurs apparaissent à la fin des opérateurs de correspondance, substitution et qr//. Par exemple, pour activer la correspondance insensible à la casse (la différence entre les lettres capitales et minuscules) :

 
Sélectionnez
my $animal = 'CaMeLiA';

like( $animal, qr/Camelia/,  'Joli papillon !'        );
like( $animal, qr/Camelia/i, 'touche Maj défectueuse' );

La première like() échouera, car les chaînes contiennent des lettres différentes. La seconde like() passera, parce que le modificateur /i dit à la regex d'ignorer les distinctions de casse. M et m sont équivalents dans la seconde regex en raison du modificateur.

Vous pouvez également intégrer des modificateurs de regex dans un motif :

 
Sélectionnez
my $trouver_chat = qr/(?<felin>(?i)chat)/;

La syntaxe (?i) active la correspondance insensible à la casse uniquement pour le groupe qu'elle englobe. Dans ce cas, c'est la capture nommée. Vous pouvez utiliser plusieurs modificateurs avec cette forme. Désactivez les modificateurs spécifiques en les faisant précéder par le caractère moins (-) :

 
Sélectionnez
my $trouver_nombre_rationnel = qr/(?<nombre>(?-i)Rat)/;

Le modificateur multiligne, /m, permet aux ancres ^ et $ de correspondre à n'importe quel saut de ligne présent dans la chaîne.

Le modificateur /s traite la chaîne source comme une seule ligne de sorte que le métacaractère . reconnaisse aussi le caractère de saut de ligne. Damian Conway suggère la mnémonique suivante : /m modifie le comportement de multiples métacaractères des expressions régulières, tandis que /s modifie le comportement d'un seul métacaractère de la regex.

Le modificateur /r provoque une opération de substitution et retourne le résultat de celle-ci, en laissant inchangée la chaîne d'origine. Si la substitution réussit, le résultat est une copie modifiée de l'original. Si la substitution échoue (parce que le motif ne correspond pas), le résultat est une copie non modifiée de l'original :

 
Sélectionnez
my $statut          = 'Envie d\'une tarte.';
my $nouveau_statut  = $statut =~ s/tarte/gateau/r;
my $copie_statut    = $statut
                    =~ s/foie aux oignons/bratwurst/r;

is( $statut, 'Envie d\'une tarte.',
    'la chaîne originale doit être inchangée' );

like( $nouveau_statut,   qr/gateau/,    'gâteau souhaité' );
unlike( $copie_statut,   qr/bratwurst/, 'pas de saucisse' );

Le modificateur /x permet d'inclure des espaces supplémentaires et des commentaires dans les motifs. Avec ce modificateur activé, le moteur de regex ignore les espaces et les commentaires, alors votre code peut être plus lisible :

 
Sélectionnez
my $attr_re = qr{
    \A                    # début de ligne

    (?:
      [;\n\s]*            # espaces et points-virgules
      (?:/\*.*?\*/)?      # commentaires en C
    )*

    ATTR

    \s+
    (   U?INTVAL
      | FLOATVAL
      | STRING\s+\*
    )
}x;

Cette regex n'est pas simple, mais les commentaires et les espaces améliorent sa lisibilité. Même si vous fusionnez des regex compilées à partir de fragments, le modificateur /x peut encore améliorer votre code.

Le modificateur /g correspond à une expression régulière globale d'une chaîne. Cela a un sens, lorsqu'il est utilisé avec une substitution :

 
Sélectionnez
# apaiser la succession de Margaret Mitchell
my $contents = slurp( $file );
$contents    =~ s/Scarlett O'Hara/Mauve Midway/g;

Lorsqu'il est utilisé avec une correspondance — pas avec une substitution, le métacaractère \G permet de traiter une chaîne dans une boucle, un morceau à la fois. \G correspond à la position où la correspondance la plus récente a pris fin. Pour traiter un fichier mal codé contenant des numéros de téléphone américains en morceaux logiques, vous pourriez écrire :

 
Sélectionnez
while ($contient =~ /\G(\w{3})(\w{3})(\w{4})/g)
{
    push @numeros, "($1) $2-$3";
}

Sachez que l'ancre \G commencera au dernier point dans la chaîne où l’occurrence d'une correspondance s'est produite dans l'itération précédente. Si l'occurrence précédente s'est terminée par une correspondance gourmande telle que .*, il restera moins de caractères disponibles dans la chaîne pour trouver une correspondance lors de la prochaine itération. Les assertions avant peuvent également aider.

Le modificateur /e vous permet d'écrire du code arbitraire du côté droit d'une substitution. Si la correspondance réussit, le moteur de regex exécutera le code, en utilisant sa valeur de retour comme valeur de substitution. L'exemple de substitution globale de tout à l'heure pourrait être plus simple avec le code suivant :

 
Sélectionnez
# apaiser la succession de Margaret Mitchell
$suite  =~ s{Scarlett( O'Hara)?}
             {
                'Mauve' . defined $1
                        ? ' Midway'
                        : ''
             }ge;

Chaque occurrence supplémentaire du modificateur /e provoquera une autre évaluation du résultat de l'expression, bien que seuls les experts Perl utilisent quelque chose au-delà de /ee.

VII-M. Reconnaissances intelligentes

L'opérateur de reconnaissance intelligente, ~~, compare deux opérandes et renvoie la valeur vraie s'il trouve une correspondance. Le type de comparaison dépend du type de deux opérandes. La construction given (Instructions switchInstructions switch) effectue une reconnaissance intelligente implicite.

À partir de Perl 5.18, cette fonctionnalité est expérimentale. Les détails de la conception actuelle sont complexes et difficiles à manier et aucune proposition de simplification des choses n'a gagné un soutien populaire suffisant pour justifier une révision complète. Plus vos opérandes sont complexes, plus vous êtes susceptible de recevoir des résultats déroutants. Évitez de comparer des objets et tenez-vous à des opérations simples entre deux scalaires ou un scalaire et une variable composite pour obtenir les meilleurs résultats.

L'opérateur de reconnaissance intelligente est un opérateur infixé :

 
Sélectionnez
say 'Ils correspondent (en quelque sorte)' if $operand_gauche ~~          
     $operand_droit;

Le type de comparaison dépend généralement d'abord du type de l'opérande droit, puis de l'opérande gauche. Par exemple, si l'opérande droit est un scalaire avec un composant numérique, la comparaison utilisera l'égalité numérique. Si l'opérande droit est une expression régulière, la comparaison utilisera un grep ou une correspondance au motif. Si l'opérande droit est un tableau, la comparaison effectuera un grep ou une reconnaissance intelligente récursive. Si l'opérande droit est un hachage, la comparaison vérifiera l'existence d'une ou plusieurs clés. Un diagramme grand et intimidant dans perldoc perlsyn donne beaucoup plus de détails sur toutes les comparaisons que la reconnaissance intelligente peut effectuer.

Ces exemples sont volontairement simples, parce que la reconnaissance intelligente peut être source de confusion :

 
Sélectionnez
my ($x, $y) = (10, 20);
say 'Inégalité numérique' unless $x ~~ $y;

my $z = '10 petits indiens';
say 'Égalité numérique' if $x ~~ $z;

my $aiguille = qr/aiguille/;

say 'Correspondance' if 'aiguille' ~~ $aiguille;

say 'Grep dans un tableau' if @botte_de_foin ~~ $aiguille;

say 'Grep des clés de hachage' if %hachage_de_foin ~~ $aiguille;

say 'Grep dans un tableau' if $aiguille ~~ @botte_de_foin;

say 'Les éléments du tableau existent en tant que clés de hachage'
    if %hachage_de_foin ~~ @botte_de_foin;

say 'Reconnaissance intelligente des éléments' if @paille ~~ @botte_de_foin;

say 'Grep des clés de hachage' if $aiguille ~~ %hachage_de_foin;

say 'Les éléments du tableau existent en tant que clés de hachage'
    if @botte_de_foin ~~ %hachage_de_foin;

say 'Clés de hachage identiques' if %hachage_de_foin ~~ %foinmap;

La reconnaissance intelligente fonctionne même si l'un des opérandes est une référence au type de données donné :

 
Sélectionnez
    say 'Clés de hachage identiques' if %hachage_de_foin ~~ \%foinmap;

Il est difficile de conseiller l'utilisation de la reconnaissance intelligente, sauf dans les circonstances les plus simples, mais elle peut être utile lorsque vous devez trouver une correspondance entre une chaîne littérale ou un nombre et une variable.


précédentsommairesuivant
Certains théoriciens universitaires des grammaires formelles préfèrent utiliser en français le terme « expressions rationnelles » pour traduire « regular expressions ». Dans la pratique, les informaticiens francophones utilisent dans leur immense majorité le terme « expressions régulières ». En outre, les expressions régulières de Perl ont tellement été étendues et enrichies (par rapport aux « expressions rationnelles » de la théorie des langages formels) qu'elles sont loin d'être « rationnelles » au sens théorique de ce mot dans ce contexte. On conservera donc ici le terme « expressions régulières ». (NdT)

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Licence Creative Commons
Le contenu de cet article est rédigé par chromatic et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Pas de Modification 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.