Introduction aux expressions régulières de Perl 5 et PCRE

Journées Perl 2014

Sébastien Tramonis-Asperghis

Petit cours sur les expressions régulières de Perl 5 et PCRE.

Commentez

- Titre : Introduction aux expressions régulières de Perl 5 et PCRE

- Auteur : Sébastien Tramonis-Asperghis

- Parution : 10 juillet 2014

- Public visé :

- Licence : Le contenu de cet article est rédigé par Sébastien Tramonis-Asperghis et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 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.

Sommaire

- I. Introduction aux expressions régulières

I - Introduction aux expressions régulières

Théorie

- Théorie des automates et langages formels.

- Grammaires de recherche de correspondances :

- DFA : Deterministic Finite Automaton ;

- NFA : Nondeterministic Finite Automaton.

- Principale différence : la gestion du retour en arrière (backtracking).

Historique

- UNIX : qed, ed, grep (BRE, puis ERE).

- POSIX regex (BRE, ERE).

- Perl 5, PCRE, Onigurama, (RE2 (DFA amélioré)).

- Perl 6.

Fonctionnement

- Correspondance de caractères.

- Motif composé de :

- caractères normaux.

"Hello World" =~ /World/;   # correspond
"Hello World" =~ /lo Wo/;   # correspond aussi
"That hat is red" =~ /hat/; # le "hat" de "That" correspond

Fonctionnement

- Correspondance de caractères.

- Motif composé de :

- caractères normaux ;

- métacaractères : {}[]()^$.|*+?\.

"2+2=4" =~ /2+2/;   # ne correspond pas
"2+2=4" =~ /2\+2/;  # correspond

"/usr/bin/perl" =~ /\/usr\/bin\/perl/;

"/usr/bin/perl" =~ m{/usr/bin/perl};

Fonctionnement

- Correspondance de caractères.

- Motif composé de :

- caractères normaux ;

- métacaractères : {}[]()^$.|*+?\ ;

- séquences d'échappement.

"1000\t2000" =~ /00\t20/;   # correspond

"cat" =~ /\143\x61\x74/;# correspond aussi (même si c'est bizarre)

Classes de caractères

- Ensemble de caractères possibles pour un emplacement de caractère.

- Notées par […].

/[bcr]at/;  # cherche "bat", "cat", "rat"

/[yY][eE][sS]/; # cherche "yes", "Yes", YES", etc.

/yes/i; # pareil, mais plus lisible

Classes de caractères

- Ensemble de caractères possibles pour un emplacement de caractère.

- Notées par […].

- Intervalles de caractères.

/[0-9]/;# équivalent à /[0123456789]/

/[a-z]/;# équivalent à /[abcde...xyz]/

/[0-9a-fA-F]/;  # chiffre hexadécimal

/item[0-9]/;# correspond à "item0", "item1"...

Classes de caractères

- Ensemble de caractères possibles pour un emplacement de caractère.

- Notées par […].

- Intervalles de caractères.

- Négation de classe : [^...].

/[^0-9]/;   # cherche un caractère qui n'est pas un chiffre

Classes de caractères

- Classes prédéfinies :

- . : tous caractères sauf \n ;

- \d : chiffre décimal ;

- \w : caractère de mot (alphanumérique plus _) ;

- \s : espace normale, tabulation, saut de ligne ;

- \h : espace horizontale ;

- \v : espace verticale ;

- \R : saut de ligne.

- Classes négatives prédéfinies :

- \D : ce qui n'est pas un chiffre décimal ;

- \W : ce qui n'est pas un caractère de mot ;

- \S : ce qui n'est pas une espace usuelle ;

- \H : ce qui n'est pas une espace horizontale ;

- \V : ce qui n'est pas une espace verticale.

Classes de caractères

- Exemples :

/\d\d:\d\d:\d\d/;   # format d'heure hh:mm:ss

/[-+]\d/;   # correspond à +2, -3, etc.

/end\./;# correspond à "end."
/end[.]/;   # pareil

Ancres

- Pour ancrer la recherche dans certains points.

- ^ : en début de chaîne.

"beausoleil" =~ /^soleil/;  # ne correspond pas

Ancres

- Pour ancrer la recherche dans certains points.

- ^ : en début de chaîne.

- $ : en fin de chaîne.

"beausoleil" =~ /beau$/;# ne correspond pas

"beausoleil" =~ /soleil$/;  # correspond

"beausoleil\n" =~ /soleil$/;# correspond aussi

Ancres

- Pour ancrer la recherche dans certains points.

- ^ : en début de chaîne.

- $ : en fin de chaîne.

- \b : frontière de mot, intervient entre \w et \W.

"beausoleil" =~ /\bbeau/;   # correspond
"beausoleil" =~ /\bbeau\b/; # ne correspond pas
"beausoleil" =~ /\bsoleil/; # ne correspond pas

Quantifieurs

- Répétition d'un sous-motif :

- * : zéro ou plusieurs fois ;

- + : une ou plusieurs fois ;

- ? : zéro ou une fois ;

- {n} : exactement n fois ;

- {n,} : au moins n fois ;

- {n,m} : entre n et m fois.

- Exemples :

"kraaack" =~ /kra+ck/;  # correspond
"kraaack" =~ /kra{1,}ck/;   # correspond aussi
"kraaack" =~ /kra{5,}ck/;   # ne correspond pas
/\w+\d{2}/; # "item04", "machine42", "Kevin68", etc.
/\d+\.\d+\.\d+\.\d+/;   # recherche simple d'une adresse IPv4
/<[-.\w]+\@[-.\w]+>/;   # recherche simple d'une adresse mail
"aaaa" =~ /a+/; # correspond avec "aaaa"

- Quantifieurs non avides :

- *? : zéro ou plusieurs fois, au plus tôt ;

- +? : une ou plusieurs fois, au plus tôt ;

- ?? : zéro ou une fois, au plus tôt ;

- {n}? : exactement n fois, au plus tôt ;

- {n,}? : au moins n fois, au plus tôt ;

- {n,m}? : entre n et m fois, au plus tôt.

- Exemples :

"aaaa" =~ /a+?/;# correspond avec "a"
^

"aaaabbbb" =~ /a+?b*?/; # correspond avec "a"
^

"aaaabbbb" =~ /a+?b+?/; # correspond avec "aaaab"
^^^^^

- Quantifieurs possessifs (nouveauté de Perl 5.10 et PCRE 7) :

- *+ : zéro ou plusieurs fois, et ne rend jamais ;

- ++ : une ou plusieurs fois, et ne rend jamais ;

- ?+ : zéro ou une fois, et ne rend jamais ;

- {n}+ : exactement n fois, et ne rend jamais ;

- {n,}+ : au moins n fois, et ne rend jamais ;

- {n,m}+ : entre n et m fois, et ne rend jamais.

- Exemples :

"aaaa" =~ /a+a/;   # /a+/ correspond avec "aaa"
^^^

"aaaa" =~ /a+?a/;  # /a+?/ correspond avec "a"
^

"aaaa" =~ /a++a/;  # /a++/ ne correspond pas

- Résumé :

"Lorem ipsum dolor sit amet"
recherche avide :          <<--| <<--| <<--| <<<<<--|
recherche non avide :      |--->>> |---> |--->> |--->
recherche possessive :     |---| |---| |---| |------|

Groupes

- Pour grouper des sous-motifs : (…).

- Par défaut, groupes capturant.

- Référencés par \1, \2… dans le motif.

- Référencés par $1, $2… en dehors du motif.

- Groupes non capturant : (?:…).

- Exemples :

/(\w+\d+ )+/;   # "item04 machine42 Kevin68 "

/(?:\d+\.){3}\d+/; # recherche simple d'une adresse IPv4

/((?:\d+\.){3}\d+)/;# recherche simple d'une adresse IPv4

/(\w+) +\1/;# recherche d'un mot répété

Alternatives

- Disjonction de sous-motifs au sein d'un groupe.

- Exemple :

/^(add|del|list) +(addr|route|rule|table) .../
# "add addr ..."
# "del rule ..."
# "list route ..."

Captures et alternatives

- Problème de la numérotation des captures.

/ ( a )  (?: x ( y ) z | (p (q) r) | (t) u (v) ) ( z ) /x
# 1            2         3  4        5     6     7

- Remise à zéro de branche : (?|..).

- Numérote les captures des branches d'une alternative comme s'il n'y en avait qu'une seule.

# before  ------------branch-reset-------------  after
/ ( a )  (?| x ( y ) z | (p (q) r) | (t) u (v) ) ( z ) /x
# 1            2         2  3        2     3     4

Captures numérotées

- Nouvelle syntaxe de référencement : \g{N}.

- N positif : numéro de capture usuel.

- N négatif : référencement arrière relatif.

- \g{-1} == précédente capture.

- Exemple :

my $find_dup = qr/ (\w+) \s+ \g{-1} /x;

Captures nommées

- (?<name>pattern) pour nommer une capture.

- \k<name>, \k{name} ou \g{name} pour s'y référer.

- Exemple :

my $find_dup = qr/ (?<dup_word>\w+) \s+ \k<dup_word> /x;

Captures nommées

- Variables lexicales %+ et %-.

- $+{name} == \g{name}.

- $-{name} == référence vers un tableau de toutes les captures de ce nom.

- Tie::Hash::NamedCapture pour avoir des noms plus clairs.

Non abordés

- Modificateurs : /i, /m,/s, /x, /p, /r, /g, /c, /a, /d, /l, /u.

- Assertion keep, \K.

- Assertion \G.

- Assertions de longueur nulle, de recherche alentour positive ou négative : (?=..), (!..), (?<=..)>, (?<!..).

Non abordés

- Ensembles de classes (?[...]).

- Évaluation de code à la volée : (?{ code }), (??{ code }).

- Verbes de contrôle : (*PRUNE), (*SKIP), (*THEN), (*COMMIT), (*FAIL), (*ACCEPT).

- Unicode.

Motifs récursifs

- Possibles en Perl 5.8, mais de manière horrible.

- Syntaxe mise au propre dans PCRE.

- Principe : réinvocation d'un groupe capturant avec (?PARNO).

- PARNO == numéro de parenthèse (groupe capturant).

- Si précédé d'un signe, compris de manière relative.

Motifs récursifs

- (?2) => le 2e groupe déclaré.

- (?-1) => dernier groupe déclaré.

- (?+1) => prochain groupe qui sera déclaré.

- (?0) ou (?R) pour invoquer à nouveau le motif complet.

- (?&name) => invoque un groupe nommé.

Motifs récursifs

- Reconnaissance de parenthèses imbriquées :

$s  = "( crack ( kapow ) ( klang ) ouch )";

$re = qr{ (           # groupe #1
\(             # parenthèse ouvrante
(?:
(?> [^()]+ )    # groupe sans retour arrière
|
(?1)            # groupe avec parenthèses
)*
\)             # parenthèse fermante
)
}x;

Motifs récursifs

- (?(condition)yes-pattern|no-pattern) => construction conditionnelle, accepte :

- un numéro de groupe (1), (2)…

- un nom de groupe <name>,

- un bout de code Perl (?{ CODE }),

- (R) pour vérifier si évaluée au sein d'une récursion,

- avec numéro ((R1), (R2)..) ou nom ((R&name)) d'un groupe pour vérifier si évaluée pendant l'exécution du groupe.

Motifs récursifs

- Cas particulier : (DEFINE).

- Seule la partie yes-pattern est autorisée.

- N'est pas directement exécutée.

- Mais peut entrer dedans en récursion.

- Permet donc de définir des fonctions de regexps.

Motifs récursifs

my $log = "192.168.1.11 -> 192.168.1.12 connect tcp 80";

$log =~ m{
(?<src_addr>(?&ip_addr))    # adresse IP source
\s+ -> \s+
(?<dest_addr>(?&ip_addr))   # adresse IP destination

(?(DEFINE)
(?<ip_addr>...)     # motif pour reconnaître une
)                       # addresse IP
}x

En ligne

- IRC : Freenode, #regex.

- https://regex101.com/.

Bibliographie

- Perl moderne par Sébastien Aperghis-Tramoni, Philippe Bruhat, Damien Krotkine, Jérôme Quelin.

- Mastering Regular Expressions, par Jeffrey E.F. Friedl.