perlre - Les expressions rationnelles en Perl
Cette page décrit la syntaxe des expressions rationnelles en Perl. Dans
Opérateurs d'expression rationnelle, vous trouverez une présentation des opérateurs m//, s///, qr// et ?? avec une description de l'usage des expressions rationnelles dans des opérations de reconnaissances
assortie de nombreux exemples. (NdT: on emploie couramment le terme
``expression régulière'' car le terme anglais est ``regular expression''
qui s'abrège en ``regexp''. Mais ne nous y trompons pas, en français, ce
sont bien des ``expressions rationnelles''.)
Les opérations de reconnaissances peuvent utiliser différents modificateurs. Les modificateurs qui concernent l'interprétation des expressions rationnelles elles-mêmes sont présentées ici. Pour les modificateurs qui modifient l'usage des expressions rationnelles fait par Perl, regarder Opérateurs d'expression rationnelle et Les détails sordides de l'interprétation des chaînes.
Reconnaissance de motif indépendamment de la casse (majuscules/minuscules).
Si use locale est actif, la table des majuscules/minuscules est celle du locale courant.
Voir perllocale.
Permet de traiter les chaînes multi-lignes. Les caractères ``^'' et ``$'' reconnaissent alors n'importe quel début ou fin de ligne plutôt qu'au début ou à la fin de la chaîne.
Permet de traiter une chaîne comme une seule ligne. Le caractère ``.'' reconnaît alors n'importe quel caractère... même une fin de ligne qui normalement n'est pas reconnue.
Les modificateurs /s et /m passent outre le réglage de $*. C'est à dire que, quelque soit le contenu de $*, /s sans /m obligent ``^'' à reconnaître uniquement le début de la chaîne et ``$'' à
reconnaître uniquement la fin de la chaîne (ou juste avant le retour à la
ligne final). Combinés, par /ms, ils permettent à ``.'' de reconnaître
n'importe quel caractère tandis que ``^'' et ``$'' reconnaissent alors
respectivement juste après ou juste avant un retour à la ligne dans la
chaîne.
Augmente la lisibilité de vos motifs en autorisant les espaces et les commentaires.
Ils sont couramment nommés « le modificateur /X », même si le délimiteur en question n'est pas la barre oblique (/). En fait, tous ces modificateurs peuvent être inclus à l'intérieur de
l'expression rationnelle elle-même en utilisant la nouvelle construction (?...). Voir plus bas.
Le modificateur /x lui-même demande un peu plus d'explication. Il demande à l'interpréteur
d'expressions rationnelles d'ignorer les espaces qui ne sont ni précédés
d'une barre oblique inverse (\) ni à l'intérieur d'une classe de caractères. Vous pouvez l'utiliser pour
découper votre expression rationnelle en parties (un peu) plus lisibles. Le
caractère # est lui aussi traité comme un méta-caractère introduisant un commentaire
exactement comme dans du code Perl ordinaire. Cela signifie que, si vous
voulez de vrais espaces ou des #
dans un motif (en dehors d'une classe de caractères qui n'est pas affectée
par
/x), vous devez les précéder d'un caractère d'échappement ou les coder en
octal ou en héxadécimal. Prises ensembles, ces fonctionnalités rendent les
expressions rationnelles de Perl plus lisibles. Faites attention à ne pas
inclure le délimiteur de motif dans un commentaire (perl n'a aucun moyen de
savoir que vous ne vouliez pas terminer le motif si tôt). Voir le code de
suppression des commentaires C dans la page de manuel perlop.
Les motifs utilisés par la mise en correspondance de motifs sont des expressions rationnelles telles que fournies dans les routines de la Version 8 des expressions rationnelles. En fait, les routines proviennent (de manière éloignée) de la réécriture gratuitement redistribuable des routines de la Version 8 par Henry Spencer. Voir Version 8 des expressions rationnelles pour de plus amples informations.
Notamment, les méta-caractères suivants gardent leur sens à la egrep:
\ Annule le meta-sens du meta-caractere qui suit
^ Reconnait le debut de la ligne
. Reconnait n'importe quel caractere (sauf le caractere nouvelle ligne)
$ Reconnait la fin de la ligne (ou juste avant le caractere nouvelle ligne final)
| Alternative
() Groupement
[] Classe de caracteres
Par défaut, le caractère ``^'' ne peut reconnaître que le début de la ligne
et le caractère ``$'' que la fin (ou juste avant le caractère nouvelle
ligne de la fin) et Perl effectue certaines optimisations en supposant que
la chaîne ne contient qu'une seule ligne. Les caractères nouvelle ligne
inclus ne seront donc pas reconnus par ``^'' ou ``$''. Il est malgré tout
possible de traiter une chaîne multi-lignes afin que ``^'' soit reconnu
juste après n'importe quel caractère nouvelle ligne et ``$'' juste avant.
Pour ce faire, au prix d'un léger retard, vous devez utiliser le
modificateur /m dans l'opérateur de reconnaissance de motif. (Les anciens programmes
obtenaient ce résultat en positionnant $* mais cette pratique est maintenant désapprouvée.)
Pour faciliter les substitutions multi-lignes, le méta-caractère ``.'' ne
reconnaît jamais un caractère nouvelle ligne à moins d'utiliser le
modificateur /s qui demande à Perl de considérer la chaîne comme une seule ligne même si ce
n'est pas le cas. Le modificateur /s passe outre le réglage de $* au cas où vous auriez quelques vieux codes qui le positionnerait dans un
autre module.
Les quantificateurs standards suivants sont reconnus:
* Reconnait 0 fois ou plus
+ Reconnait 1 fois ou plus
? Reconnait 0 ou 1 fois
{n} Reconnait n fois exactement
{n,} Reconnait au moins n fois
{n,m} Reconnait au moins n fois mais pas plus de m fois
(Si une accolade apparaît dans n'importe quel autre contexte, elle est
traitée comme un caractère normal.) Le quantificateur ``*'' est équivalent
à {0,}, le quantificateur ``+'' à {1,} et le quantificateur ``?'' à {0,1}. n et m sont limités à des valeurs entières (!) inférieures à une valeur
fixée lors de la compilation de perl. Habituellement cette valeur est 32766
sur la plupart des plateformes. La limite réelle peut être trouvée dans le
message d'erreur engendré par le code suivant :
$_ **= $_ , / {$_} / for 2 .. 42;
Par défaut, un sous-motif quantifié est « gourmand », c'est à dire qu'il essaie de se reconnaître un maximum de fois (à partir d'un point de départ donné) sans empêcher la reconnaissance du reste du motif. Si vous voulez qu'il tente de se reconnaître un minimum de fois, il faut ajouter le caractère ``?'' juste après le quantificateur. Sa signification ne change pas. Seule sa « gourmandise » change:
*? Reconnait 0 fois ou plus
+? Reconnait 1 fois ou plus
?? Reconnait 0 ou 1 fois
{n}? Reconnait n fois exactement
{n,}? Reconnait au moins n fois
{n,m}? Reconnait au moins n fois mais pas plus de m fois
Puisque les motifs sont traités comme des chaînes entre guillemets, les séquences suivantes fonctionnent :
\t tabulation (HT, TAB)
\n nouvelle ligne (LF, NL)
\r retour chariot (CR)
\f page suivante (FF)
\a alarme (bip) (BEL)
\e escape (pensez a troff)(ESC)
\033 caractere en octal (pensez au PDP-11)
\x1B caractere hexadecimal
\c[ caractere de controle
\l converti en minuscule le caractere suivant (pensez a vi)
\u converti en majuscule le caractere suivant (pensez a vi)
\L converti en minuscule jusqu'au prochain \E (pensez a vi)
\U converti en majuscule jusqu'au prochain \E (pensez a vi)
\E fin de modification de casse (pensez a vi)
\Q desactive les meta-caracteres de motif jusqu'au prochain \E
Si use locale est actif, la table de majuscules/minuscules utilisée par
\l, \L, \u et \U est celle du locale courant. Voir perllocale.
Vous ne pouvez pas inclure littéralement les caractères $ et @ à l'intérieur d'une séquence \Q. Tels quels, ils se référeraient à la variable correspondante. Précédés
d'un \, ils correspondraient à la chaîne
\$ ou \@. Vous êtes obligés d'écrire quelque chose comme
m/\Quser\E\@\Qhost/.
Perl définit aussi les séquences suivantes:
\w Reconnait un caractere de "mot" (alphanumerique plus "_")
\W Reconnait un caractere de non-"mot"
\s Reconnait un caractere d'espace.
\S Reconnait un caractere autre qu'un espace
\d Reconnait un chiffre
\D Reconnait un caractere autre qu'un chiffre
\w reconnaît un seul caractère alphanumérique, pas à un mot entier. Pour
reconnaître un mot entier, vous devez utiliser \w+. Si use locale est actif, la liste de caractères alphanumériques utilisés par \w dépend du locale courant. Voir perllocale. Vous pouvez utiliser \w, \W, \s,
\S, \d, et \D à l'intérieur d'une classe de caractères (mais pas comme borne d'un
intervalle).
Perl définit les assertions de longueur nulle suivantes:
\b Reconnait la limite d'un mot
\B Reconnait autre chose qu'une limite de mot
\A Reconnait uniquement le debut de la chaine
\Z Reconnait uniquement la fin de la chaine (ou juste avant le
caractere de nouvelle ligne final)
\z Reconnait uniquement la fin de la chaine
\G Reconnait l'endroit ou s'est arrete le precedent m//g
(ne fonctionne qu'avec /g)
Une limite de mot (\b) est défini comme le point entre deux caractères qui sont d'un côté un \w et de l'autre un \W (dans n'importe quel ordre). Le début et la fin de la chaîne correspondent
à des caractères imaginaires de type \W. (À l'intérieur d'une classe de caractères, \b représente le caractère ``backspace'' au lieu d'une limite de mot.) \A et \Z agissent exactement comme ``^'' et ``$'' sauf qu'ils ne reconnaissent pas
les lignes multiples quand le modificateur /m est utilisé alors que ``^'' et ``$'' reconnaissent toutes les limites de
lignes internes. Pour reconnaître la fin réelle de la chaîne, en tenant
compte du caractère nouvelle ligne, vous devez utiliser \z.
\G est utilisé pour enchaîner plusieurs mises en correspondance (en utilisant m//g). Voir Opérateurs d'expression rationnelle. Il est aussi utile lorsque vous écrivez un analyseur lexicographique à la lex ou lorsque vous avez plusieurs motifs qui doivent reconnaître des
sous-chaînes successives de votre chaîne. Voir la référence précédente.
L'endroit réel à partir duquel \G va être reconnu peut être modifié en affectant une nouvelle valeur à pos(). Voir pos.
Quand la mémorisation par parenthèses ( ... ) est utilisée, \n correspond à la n-ième sous-chaîne mémorisée. À
l'extérieur du motif, utilisez toujours ``$'' à la place de ``\'' devant n.
(Bien que la notation \n fonctionne en de rares occasions à l'extérieur du
motif courant, vous ne devriez pas compter dessus. Voir l'avertissement
plus loin.) La portée de \n (ainsi que de $`, $&, et $') s'étend jusqu'à la fin du bloc englobant ou de l'évaluation d'une chaîne
ou jusqu'à la prochaine reconnaissance de motif réussie selon ce qui a lieu
en premier. Si vous voulez utilisez les parenthèses pour délimiter un
sous-motif (e.g., un ensemble d'alternatives) sans mémoriser ce sous-motif,
faîtes suivre la parenthèse ( par ?:.
Vous pouvez utiliser autant de parenthèses que nécessaire. Si vous avez plus de 9 sous-chaînes, les variables $10, $11, etc. se réfèrent aux sous-chaînes correspondantes. À l'intérieur du motif, \10, \11, etc. se réfèrent aux sous-chaînes précédentes dans la mesure où il y a suffisamment de parenthèses gauches qui précèdent cette référence arrière. Sinon (pour assurer la compatibilité ascendante) \10 sera interprété comme \010 (un backspace), \11 comme \011 (une tabulation) et ainsi de suite... (les séquences de \1 à \9 sont toujours interprétées comme des références arrières.)
$+ renvoie le dernier sous-motif entre parenthèses reconnu. $& renvoie le dernier motif reconnu. ($0 était utilisé pour le même usage mais ne l'est plus.) $` renvoie tout ce qui est avant le motif reconnu. $'
renvoie tout ce qui est après le motif reconnu. Exemples:
s/^([^ ]*) *([^ ]*)/$2 $1/; # echange les deux premiers mots
if (/Time: (..):(..):(..)/) {
$hours = $1;
$minutes = $2;
$seconds = $3;
}
À partir du moment où perl voit que vous avez besoin de l'une des variables
$&, $` ou $' quelque part dans votre programme, il les calculera à chaque reconnaissance
de motif et ce pour tous les motifs. Cela peut ralentir votre programme. Le
même mécanisme est utilisé lors de l'utilisation de $1, $2, etc.. Ce
ralentissement a donc lieu aussi pour les motifs mémorisant des
sous-motifs. Mais si vous n'utilisez pas $&, etc. dans vos scripts
alors vos motifs sans mémorisation ne seront pas pénalisés. Donc, évitez $&, $' et $` si vous
le pouvez. Dans le cas contraire (et certains algorithmes en ont réellement
besoin), si vous les utilisez une fois, utilisez-les partout car vous en
supportez déjà le coût. Depuis la version 5.005, $& n'est plus aussi
coûteux que les deux autres.
Les méta-caractères précédés d'un caractère barre oblique inversée en Perl
sont alphanumériques tels \b, \w, \n. À l'inverse d'autres langages d'expressions rationnelles, il n'y a pas de
symbole précédé d'un caractère barre oblique inversée qui ne soit pas
alphanumérique. Donc tout ce qui ressemble à \\, \(, \), \<, \>, \{, ou \} est toujours interprété littéralement et non comme un
méta-caractère. Ceci est utilisé pour désactiver (ou «quoter») les
éventuels méta-caractères présents dans une chaîne que vous voulez utiliser
comme motif. Tout simplement, il vous suffit de précéder tous les caractère
non-alphanumériques par un caractère barre oblique inversée:
$pattern =~ s/(\W)/\\$1/g;
Il est encore plus simple d'utiliser soit le fonction
quotemeta() soit la séquence \Q pour désactiver les éventuels méta-caractères:
/$unquoted\Q$quoted\E$unquoted/
Perl définit une syntaxe d'extension logique des expressions rationnelles. La syntaxe est une paire de parenthèses avec un point d'interrogation comme premier caractère entre les parenthèses (c'était une erreur de syntaxe dans les versions plus anciennes de Perl). Le caractère qui suit le point d'interrogation précise la fonction de l'extension. Plusieurs extensions sont déjà supportées:
Un commentaire. Le texte est ignoré. Si le modificateur /x est utilisé pour autoriser la mise en forme avec des blancs, un simple # devrait suffire. Notez que Perl termine le commentaire aussitôt qu'il
rencontre
). Il n'y a donc aucun moyen de mettre le caractère ) dans ce commentaire.
C'est pour regrouper et non pas mémoriser. Cela permet de regrouper des sous-expressions comme ``()'' mais sans permettre les références arrière. Donc
@fields = split(/\b(?:a|b|c)\b/)
est similaire à
@fields = split(/\b(a|b|c)\b/)
mais ne produit pas de mémorisations supplémentaires.
Les lettres entre ? et : agissent comme des modificateurs. Voir
(?imsx-imsx). Notamment
/(?s-i:more.*than).*million/i
est équivalent à
/(?:(?s-i)more.*than).*million/i
Une assertion de longueur nulle pour tester la présence de quelque chose en
avant. Par exemple, /\w+(?=\t)/ reconnaît un mot suivit d'une tabulation sans inclure cette tabulation dans $&.
Une assertion de longueur nulle pour tester l'absence de quelque chose en
avant. Par exemple, /foo(?!bar)/ reconnaît toutes les occurrences de ``foo'' qui ne sont pas suivies de
``bar''. Notez bien que regarder en avant n'est pas la même chose que
regarder en arrière (!). Vous ne pouvez pas utiliser cette assertion pour
regarder en arrière.
Pour chercher ``bar'' qui ne soit pas précédé par ``foo'', /(?!foo)bar/ ne vous donnera pas ce que vous voulez. C'est parce que (?!foo) exige seulement que ce qui suit ne soit pas ``foo'' -- et ça ne l'est pas
puisque c'est ``bar'', donc ``foobar'' sera accepté. Vous devez alors
utilisez quelque chose comme
/(?!foo)...bar/. Nous disons ``comme'' car il se peut que ``bar'' ne soit pas précédé par
trois caractères. Vous pouvez alors utiliser
/(?:(?!foo)...|^.{0,2})bar/. Parfois, il est quand même plus simple de dire:
if (/bar/ && $` !~ /foo$/)
Pour regarder en arrière, voir plus loin.
Une assertion de longueur nulle pour tester la présence de quelque chose en
arrière. Par exemple, /(?<=\t)\w+/ reconnaît un mot qui suit une tabulation sans inclure cette tabulation dans $&. Cela ne fonctionne qu'avec un motif de longueur fixe.
Une assertion de longueur nulle pour tester l'absence de quelque chose en
arrière. Par exemple, /(?<!bar)foo/ reconnaît toutes les occurrences de ``foo'' qui ne suivent pas ``bar''.
Cela ne fonctionne qu'avec un motif de longueur fixe.
Une assertion expérimentale de longueur nulle permettant « l'évaluation de
code Perl ». Elle est reconnue dans tous les cas. Actuellement les règles
pour déterminer où le code se termine sont quelque peu compliquées.
Le code a une portée correcte : si il y a un retour arrière sur l'assertion (voir Retour arrière), tous les changements introduits après la
localisation sont annulés. Donc
$_ = 'a' x 8;
m<
(?{ $cnt = 0 }) # Initialisation de $cnt.
(
a
(?{
local $cnt = $cnt + 1; # Mise a jour de $cnt,
# (en tenant compte du retour arriere)
})
)*
aaaa
(?{ $res = $cnt }) # En cas de succes, recopie vers une
# variable non local-isee.
>x;
produit $res = 4. Remarquez qu'après la reconnaissance, $cnt revient à la
valeur 0 affectée globalement puisque nous ne sommes plus dans la portée du
local.
Cette assertion peut être utilisée comme sélecteur:
(?(condition)motif-oui|motif-non). Si elle n'est pas utilisée comme cela, le résultat de l'évaluation du code est affecté à la variable $^R. L'affectation est immédiate donc $^R
peut-être utilisé à partir d'une autre assertion de type (?{ code }) à l'intérieur de la même expression rationnelle.
L'affectation à $^R est correctement localisée, par conséquent l'ancienne valeur de $^R est restaurée en cas de retour arrière (voir Retour arrière).
Pour des raisons de sécurité, cette construction n'est pas autorisée si
l'expression rationnelle nécessite une interpolation de variables lors de
l'exécution sauf si vous utilisez le directive use re 'eval' (voir re) ou si la variable contient le résultat de l'opérateur qr()
(voir
qr/STRING/imosx).
Cette restriction est due à l'usage très courant (mais déconseillé) de la construction suivante :
$re = <>;
chomp $re;
$string =~ /$re/;
sans contrôle de corruption (tainting). Même si ce code est déjà mauvais du
point de vue de la sécurité, à l'introduction de (?{}), on considéra inutile d'ajouter un nouveau trou de sécurité aux scripts existants.
REMARQUE: l'utilisation de l'information précédente sans activer le contrôle de
corruption est sévèrement désapprouvée. use re 'eval' ne supprime pas le contrôle de corruption. Par conséquent pour autoriser
$re à contenir des (?{}) dans le bout de code précédent avec le contrôle
actif, il faut à la fois utiliser use re 'eval' et supprimer le contrôle sur $re (untaint).
Une sous-expression ``indépendante''. Reconnaît uniquement la sous-chaîne qui aurait été reconnue si la sous-expression avait été seule et ancrée au même endroit.
Par exemple, ^(?>a*)ab ne pourra jamais être reconnu puisque
(?>a*) (ancré au début de la chaîne) reconnaîtra tous les caractères
a du début de la chaîne en ne laissant aucun a pour reconnaître ab. A contrario, a*ab reconnaîtra la même chose que a+b puisque la reconnaissance du sous-groupe a* est influencé par le groupe suivant ab
(voir Retour arrière). En particulier, a* dans a*ab reconnaît moins de caractères que a* seul puisque cela permet à la suite d'être reconnue.
Un effet similaire à (?>motif) peut être obtenu grâce à
(?=(motif))\1
puisque la recherche est effectuée dans un contexte "logique" et reconnaît donc la même sous-chaîne qu'une expression seule. Le \1 mange la chaîne reconnue et transforme donc l'assertion de longueur nulle
en une expression analogue à (?>...). (La seule différence est que la dernière utilise un groupe mémorisé et
décale donc le numéro des références arrière dans le reste de l'expression
rationnelle.
Cette construction est utile pour l'optimisation car elle n'effectue pas de retour-arrière (voir Retour arrière).
m{ \(
(
[^()]+
|
\( [^()]* \)
)+
\)
}x
L'exemple précédent reconnaît de manière efficace un groupe non-vide qui
contient au plus deux niveaux de parenthèses. Par contre, si un tel groupe
n'existe pas, cela prendra un temps potentiellement infini sur une longue
chaîne car il existe énormément de manières différentes de découper une
chaîne en sous-chaînes. C'est ce que fait (.+)+ qui est similaire à l'un des sous-motifs du motif précédent. Rendez-vous
compte que l'expression précédente détecte la non reconnaissance sur ((()aaaaaaaaaaaaaaaaaa en quelques secondes mais que chaque lettre supplémentaire multiplie ce
temps par deux. Cette augmentation exponentielle du temps d'exécution peut
faire croire que votre programme est planté.
Par contre, le même motif très légèrement modifié
m{ \(
(
(?> [^()]+ )
|
\( [^()]* \)
)+
\)
}x
et utilisant (?>...) reconnaît exactement la même chose (un bon exercice serait de la vérifier
vous-même) mais se termine en 4 fois moins de temps sur une chaîne
similaire contenant 1000000 a. Attention, ce motif produit actuellement un message d'avertissement avec -w disant
"matches the null string many times" ("reconnaît la chaîne de longueur
nulle très souvent").
Dans de petits motifs comme (? [^()]+ )>, un effet comparable peut être observé en utilisant le test
d'absence en avant comme dans [^()]+ (?!
[^()] ). Ce n'est que 4 fois plus lent sur une chaîne contenant 1000000
a.
Expression conditionnelle. (condition) est soit un entier entre parenthèses (qui est vrai si le sous-motif
mémorisé correspondant reconnaît quelque chose), soit une assertion de
longueur nulle (test en arrière, en avant ou évaluation).
Par exemple,
m{ ( \( )?
[^()]+
(?(1) \) )
}x
reconnaît une suite de caractères tous différents des parenthèses éventuellement entourée d'une paire de parenthèses.
Un ou plusieurs modificateurs inclus. C'est particulièrement pratique pour
stocker une table de motifs lorsque certains doivent être sensibles à la
casse et d'autres non. Il suffit tout simplement de préfixer ces derniers
par (?i). Exemple:
$pattern = "foobar";
if ( /$pattern/i ) { }
# plus general :
$pattern = "(?i)foobar";
if ( /$pattern/ ) { }
Les lettres après un - annulent les modifications correspondantes.
Ces modificateurs n'agissent qu'à l'intérieur du groupe qui les englobe (s'il existe). Donc,
( (?i) blah ) \s+ \1
(en supposant qu'en dehors de ce groupe, le modificateur x est actif mais pas le modificateur i) reconnaîtra le mot blah (dans n'importe quelle casse) répété (avec la même casse).
Nous avons choisi le point d'interrogation pour ça et pour la nouvelle construction de reconnaissance minimale d'une part parce que le point d'interrogation est relativement rare dans les anciennes expressions rationnelles et d'autre part pour qu'à chaque fois que vous en voyez un vous vous arrêtiez pour vous interroger sur son rôle. C'est psychologique...
Une particularité fondamentale de la reconnaissance d'expressions
rationnelles est liée à la notion de retour arrière qui est actuellement utilisée (si nécessaire) par tous les quantificateurs
d'expressions rationnelles. À savoir *, *?, +, +?, {n,m} et {n,m}?.
Pour qu'une expression rationnelle soit reconnue, toute l'expression doit être reconnue pas seulement une partie. Donc si le début de l'expression rationnelle est reconnue mais de telle sorte qu'elle empêche la reconnaissance de la suite du motif, le moteur de reconnaissance revient en arrière pour calculer autrement le début -- d'où le nom retour arrière.
Voici un exemple de retour arrière. Supposons que vous voulez trouver le mot qui suit ``foo'' dans la chaîne ``Food is on the foo table.'':
$_ = "Food is on the foo table.";
if ( /\b(foo)\s+(\w+)/i ) {
print "$2 suit $1.\n";
}
Lors de la reconnaissance, la première partie de l'expression rationnelle (\b(foo)) trouve un point d'ancrage dès le début de la chaîne et stocke ``Foo''
dans $1. Puis le moteur de reconnaissance s'aperçoit qu'il n'y pas de
caractère d'espacement après le ``Foo'' qu'il a stocké dans $1
et, reconnaissant son erreur, il recommence alors un caractère plus loin
que cette première tentative. Cette fois, il va jusqu'à la prochaine
occurrence de ``foo'', l'ensemble de l'expression rationnelle est reconnue
et vous obtenez comme prévu ``table suit foo.''
Parfois la reconnaissance minimale peut aider. Imaginons que vous voulez reconnaître tout ce qu'il y a entre ``foo'' et ``bar''. Tout d'abord, vous écrivez quelque chose comme:
$_ = "The food is under the bar in the barn.";
if ( /foo(.*)bar/ ) {
print "got <$1>\n";
}
qui de manière inattendue produit:
got <d is under the bar in the >
C'est parce que .* est gourmand. Vous obtenez donc tout ce qu'il y a entre le premier ``foo'' et le dernier ``bar''. Dans ce cas, la reconnaissance minimale est efficace pour vous
garantir de reconnaître tout ce qu'il y a entre un ``foo'' et le premier
``bar'' qui suit.
if ( /foo(.*?)bar/ ) { print "got <$1>\n" }
got <d is under the >
Voici un autre exemple. Supposons que vous voulez reconnaître un nombre à la fin d'une chaîne tout en mémorisant ce qui précède. Vous écrivez donc:
$_ = "I have 2 numbers: 53147";
if ( /(.*)(\d*)/ ) { # Rate!
print "Beginning is <$1>, number is <$2>.\n";
}
Cela ne marche pas parce que .* est gourmand et engloutit toute la chaîne. Puisque \d* peut reconnaître la chaîne vide, toute l'expression rationnelle est
reconnue.
Beginning is <I have 2 numbers: 53147>, number is <>.
Voici quelques variantes dont la plupart ne marche pas:
$_ = "I have 2 numbers: 53147";
@pats = qw{
(.*)(\d*)
(.*)(\d+)
(.*?)(\d*)
(.*?)(\d+)
(.*)(\d+)$
(.*?)(\d+)$
(.*)\b(\d+)$
(.*\D)(\d+)$
};
for $pat (@pats) {
printf "%-12s ", $pat;
if ( /$pat/ ) {
print "<$1> <$2>\n";
} else {
print "FAIL\n";
}
}
qui affichera:
(.*)(\d*) <I have 2 numbers: 53147> <>
(.*)(\d+) <I have 2 numbers: 5314> <7>
(.*?)(\d*) <> <>
(.*?)(\d+) <I have > <2>
(.*)(\d+)$ <I have 2 numbers: 5314> <7>
(.*?)(\d+)$ <I have 2 numbers: > <53147>
(.*)\b(\d+)$ <I have 2 numbers: > <53147>
(.*\D)(\d+)$ <I have 2 numbers: > <53147>
Comme vous pouvez le constater, c'est un peu délicat. Il faut comprendre qu'une expression rationnelle n'est qu'un ensemble d'assertions donnant une définition du succès. Il peut y avoir aucun, un ou de nombreux moyens de répondre à cette définition lorsqu'on l'applique à une chaîne particulière. Et lorsqu'il y a plusieurs moyens, vous devez comprendre le retour arrière pour savoir quelle variété de succès vous obtiendrez.
Avec l'utilisation des assertions de tests d'absence ou de présence, cela peut devenir carrément épineux. Supposons que vous voulez retrouver une suite de caractères non-numériques suivie par ``123''. Vous pouvez essayer d'écrire quelque chose comme :
$_ = "ABC123";
if ( /^\D*(?!123)/ ) { # Rate!
print "Heu, pas de 123 dans $_\n";
}
Mais ça ne fonctionne pas... tout du moins pas comme vous l'espériez. Ça affiche qu'il n'y a pas de 123 à la fin de la chaîne. Voici une présentation nette de ce que ces motifs reconnaissent contrairement à ce qu'on pourrait attendre:
$x = 'ABC123' ;
$y = 'ABC445' ;
print "1: got $1\n" if $x =~ /^(ABC)(?!123)/ ;
print "2: got $1\n" if $y =~ /^(ABC)(?!123)/ ;
print "3: got $1\n" if $x =~ /^(\D*)(?!123)/ ;
print "4: got $1\n" if $y =~ /^(\D*)(?!123)/ ;
Affiche:
2: got ABC
3: got AB
4: got ABC
On aurait pu croire que le test 3 échouerait puisqu'il semble être une
généralisation du test 1. La différence importante entre les deux est que
le test 3 contient un quantificateur (\D*) et peut donc effectuer des retours arrière alors que le test 1 ne le peut
pas. En fait, ce que demande le test 3 c'est ``existe-t-il au début de $x quelque chose qui n'est pas 123 et qui suit 0 ou
plusieurs caractères autres que des chiffres ?''. Si on laisse \D* reconnaître ``ABC'', cela entraîne l'échec du motif complet. Le moteur de
reconnaissance met tout d'abord en correspondance
\D* avec ``ABC''. Puis il essaie de mettre en correspondance
C(?!123> avec ``123'', ce qui évidemment échoue. Mais puisque
un quantificateur (\D*) est utilisé dans l'expression rationnelle, le moteur de reconnaissance
peut faire des retours arrière pour tenter une reconnaissance différente
afin de reconnaître l'ensemble de l'expression rationnelle.
Le motif veut vraiment être reconnu, alors il utilise le mécanisme de retour arrière et limite
cette fois l'expansion de \D* juste à ``AB''. Maintenant, il y a réellement quelque chose qui suit ``AB''
et qui n'est pas ``123. C'est ''C123`` qui est suffisant pour réussir.
On peut réussir en mixant l'utilisation des assertions de présence et d'absence. Nous dirons alors que la première partie doit être suivie par un chiffre mais doit aussi être suivie par quelque chose qui n'est pas ``123''. Souvenez-vous que les tests en avant sont des assertions de longueur nulle -- ils ne font que regarder mais ne consomment pas de caractères de la chaîne lors de leur reconnaissance. Donc, réécrit comme suit, cela produira le résultat attendu. C'est à dire que le test 5 échouera et le 6 marchera.
print "5: got $1\n" if $x =~ /^(\D*)(?=\d)(?!123)/ ;
print "6: got $1\n" if $y =~ /^(\D*)(?=\d)(?!123)/ ;
6: got ABC
En d'autres termes, cela signifie que les deux assertions de longueur nulle
consécutives doivent être vraies toutes les deux ensembles, exactement
comme quand vous utilisez des assertions prédéfinies: /^$/ est reconnue uniquement si vous êtes simultanément au début ET à la fin de
la ligne. La réalité sous-jacente est que la juxtaposition de deux
expressions rationnelles signifie toujours un ET sauf si vous écrivez
explicitement un OU en utilisant la barre verticale. /ab/ signifie reconnaître ``a'' ET (puis) reconnaître ``b'', même si les
tentatives de reconnaissance n'ont pas lieu à la même position puisque
``a'' n'est pas une assertion de longueur nulle (mais de longueur un).
Un avertissement: certaines expressions rationnelles compliquées peuvent prendre un temps exponentiel pour leur résolution à cause du grand nombre de retours arrière effectués pour essayer d'être reconnue. Par exemple, l'expression suivante peut calculer très longtemps
/((a{0,5}){0,5}){0,5}/
et si vous utilisez des * au lieu de limiter entre 0 et 5 reconnaissances, elle peut alors prendre
littéralement un temps infini -- ou plutôt jusqu'au dépassement de la
capacité de la pile. Un outil puissant pour optimiser ce genre de choses
est les groupes ``indépendants'' qui n'effectuent pas de retour arrière
(voir
(?>motif)). Remarquez aussi que les assertions de longueur nulle n'effectuent pas de
retour arrière puisqu'elles sont considérées dans un contexte ``logique'':
seul importe qu'elles soient reconnaissables ou non. Pour un exemple de
l'influence éventuelle sur la reconnaissance voir
(?>motif).
Si vous n'êtes pas familier avec la version 8 des expressions rationnelles, vous trouverez ici les règles de reconnaissance de motifs qui n'ont pas été décrites plus haut.
Un caractère se reconnaît lui-même sauf si c'est un méta-caractère avec un
sens spécial décrit ici ou précédemment. Pour interpréter un méta-caractère
littéralement, il faut le préfixer par un ``\'' (e.g., ``\.'' reconnaît un
``.'' au lieu de n'importe quel caractère, ``\\'' reconnaît ``\''). Une
suite de caractères reconnaît cette suite de caractères dans la chaîne à
analyser. Le motif
blurfl reconnaîtra donc ``blurfl'' dans la chaîne à analyser.
Vous pouvez spécifier une classe de caractères en entourant une liste de
caractères entre []. Cette classe reconnaîtra l'un des caractères de la liste. Si le premier
caractère après le ``['' est ``^'', la classe reconnaîtra un caractère qui
n'est pas dans la liste. À l'intérieur d'une liste, la caractère ``-'' sert
à décrire un intervalle. Par exemple a-z représente tous les caractères entre ``a'' et ``z'' inclus. Si vous voulez
inclure dans la classe le caractère ``-'' lui-même, mettez le au début ou à
la fin de la liste ou préfixez le par un ``\''. (Les exemples suivants
décrivent tous la même classe de trois caractères: [-az], [az-], [a\-z]. Ils sont tous différents de
[a-z] qui décrit une classe contenant 26 caractères.)
Remarquez aussi que le concept de classe de caractères n'est pas vraiment portable entre différents codages -- et même dans un même codage, cela peut produire un résultat que vous n'attendez pas. Un bon principe de base est de n'utiliser que des intervalles qui commencent et se terminent dans le même alphabet ([a-e], [A-E]) ou dans les chiffres ([0-9]). Tout le reste n'est pas sûr. Dans le doute, énumérez l'ensemble des caractères explicitement.
Certains caractères peuvent être spécifiés avec la syntaxe des méta-caractères comme en C: ``\n'' reconnaît le caractère nouvelle ligne, ``\t'' reconnaît une tabulation, ``\r'' reconnaît un retour chariot, ``\f'' reconnaît un changement de page, etc. Plus généralement, \nnn, où nnn est une suite de chiffres en octaux, reconnaît le caractère dont le code ASCII est nnn. De même, \xnn, où nn est une suite de chiffres héxadécimaux, reconnaît le caractère dont le code ASCII est nn. L'expression \cx reconnaît le caractère ASCII control-x. Pour terminer, le méta-caractère ``.'' reconnaît n'importe quel caractère sauf ``\n'' (à moins d'utiliser /s).
Vous pouvez décrire une série de choix dans un motif en les séparant par
des ``|''. Donc fee|fie|foe reconnaîtra n'importe quel ``fee'', ``fie'' ou ``foe'' dans la chaîne à
analyser (comme le ferait f(e|i|o)e). Le premier choix inclut tout ce qui suit le précédent délimiteur de
motif (``('', ``['' ou le début du motif) jusqu'au premier ``|'' et le
dernier choix inclut tout ce qui suit le dernier ``|'' jusqu'au prochain
délimiteur de motif. C'est pour cette raison qu'il est courant d'entourer
les choix entre parenthèses pour minimiser les risques de mauvaises
interprétation de début et de fin.
Les différents choix sont essayés de la gauche vers la droite et le premier
choix qui autorise la reconnaissance de l'ensemble du motif est retenu. Ce
qui signifie que les choix ne sont pas obligatoirement ``gourmand''. Par
exemple: en appliquant foo|foot à ``barefoot'', seule la partie ``foo'' est reconnue puisque c'est le
premier choix essayé et qu'il permet la reconnaissance du motif complet.
(Cela peu sembler anodin mais ne l'est pas lorsque vous mémorisez du texte
avec les parenthèses.)
Souvenez-vous aussi que ``|'' est interprété littéralement lorsqu'il est
présent dans une classe de caractères. Donc si vous écrivez [fee|fie|foe], vous recherchez en fait [feio|].
À l'intérieur d'un motif, vous pouvez mémoriser ce que reconnaît un
sous-motif en l'entourant de parenthèses. Plus loin dans le motif, vous
pouvez faire référence au n-ieme sous-motif mémorisé en utilisant le méta-caractère \n. Les sous-motifs sont numérotés de droite à gauche dans l'ordre de leurs
parenthèses ouvrantes. Une référence reconnaît exactement la chaîne
actuellement reconnue par le sous-motif correspondant et non pas n'importe
quelle chaîne qu'il aurait pu reconnaître. Par conséquent, (0|0x)\d*\s\1\d*
reconnaîtra ``0x1234 0x4321'' mais pas ``0x1234 01234'' car, même si (0|0x)
peut reconnaître le 0 dans le second nombre, ce sous-motif reconnaît dans
ce cas ``0x''.
De nombreuses personnes ont l'habitude d'écrire des choses comme :
$pattern =~ s/(\W)/\\\1/g;
Dans la partie droite d'une substitution, ce sont des vieilleries pour ne
pas choquer les fanas de sed mais ce sont de mauvaises habitudes. Parce que du point du vue Perl, la
partie droite d'un s/// est considérée comme une chaîne entre guillemets. \1 dans une chaîne entre guillemets signifie control-A. Le sens Unix habituel
de \1 est kludged dans s///. Par contre, si vous prenez cette mauvaise habitude, vous serez très
perturbé si vous ajoutez le modificateur /e :
s/(\d+)/ \1 + 1 /eg; # provoque un "warning" avec -w
ou si vous essayez :
s/(\d+)/\1000/;
Vous ne pouvez lever l'ambiguïté en écrivant \{1}000 alors que c'est possible grâce à ${1}000. En fait, il ne faut pas confondre l'opération d'interpolation et
l'opération de reconnaissance d'une référence. Elles ont certainement deux
sens bien différents dans la partie gauche de s///.
AVERTISSEMENT: ce qui suit est difficile. Cette section doit être réécrite.
Les expressions rationnelles fournissent un langage de programmation laconique et puissant. Comme avec la plupart des autres outils puissants, ce pouvoir peut faire des ravages.
Un fréquent ``abus de pouvoir'' découle de la possibilité de créer des boucles sans fin avec des expressions rationnelles aussi innocentes que:
'foo' =~ m{ ( o? )* }x;
o? peut être reconnu au début de 'foo' et, puisque la position dans la chaîne n'est pas modifiée par la
reconnaissance, o? sera reconnu indéfiniment à cause du modificateur *. L'utilisation du modificateur
//g est un autre moyen courant d'obtenir de telle cycle:
@matches = ( 'foo' =~ m{ o? }xg );
ou
print "match: <$&>\n" while 'foo' =~ m{ o? }xg;
ou encore dans la boucle induite par split().
Par contre, une longue expérience montre que de nombreuses tâches de programmation peuvent vraiment se simplifier grâce à la reconnaissance répétée de sous-expressions éventuellement de longueur nulle. En voici un exemple simple:
@chars = split //, $string; # // n'est pas magique pour split
($whitewashed = $string) =~ s/()/ /g; # les parentheses supprime la magie de s// /
Donc Perl autorise la construction /()/ qui rompt de force la boucle
infinie. Les règles pour les boucles de bas niveau obtenues par les modificateurs
gourmands *+{} sont différentes de celles de haut niveau induites par exemples par /g ou par l'opérateur split().
Les boucles de bas niveau sont interrompues lorsqu'on détecte la répétition d'une expression reconnaissant une sous-chaîne de longueur nulle. Donc
m{ (?: NON_ZERO_LENGTH | ZERO_LENGTH )* }x;
est en fait équivalent à
m{ (?: NON_ZERO_LENGTH )*
|
(?: ZERO_LENGTH )?
}x;
Les boucles de haut niveau se souviennent entre les itérations que la dernière chaîne reconnue était de longueur nulle. Pour briser la boucle infinie, une chaîne reconnue ne peut-être de longueur nulle si la précédente l'était. Cette interdiction interfère avec le retour arrière (voir Retour arrière) et dans ce cas, la seconde meilleure chaîne est choisie si la meilleure est de longueur nulle.
Donc,
$_ = 'bar';
s/\w??/<$&>/g;
produit "<<b><><a><><r><>``>. À chaque position dans la chaîne, la meilleur chaîne reconnue
par le sobre ?? est la chaîne de longueur nulle et la seconde meilleure chaîne est celle
reconnue par \w. Donc les reconnaissances de longueur nulle alternent avec celles d'un
caractère de long.
De manière similaire, pour des m/()/g répétés, la seconde meilleure reconnaissance est un cran plus loin dans la
chaîne.
La mémorisation de l'état précédente reconnaissance de longueur nulle
est liée à chaque chaîne explorée. De plus, elle est réinitialisée à chaque
affectation de pos().
La surcharge de constantes (voir surcharge) est un moyen simple d'augmenter les fonctionnalités du moteur RE (le moteur de reconnaissance d'expressions rationnelles).
Imaginons que vous voulez définir une nouvelle séquence \Y| qui est reconnue à la limite entre un espace et un autre caractère.
Remarquez que
(?=\S)(?<!\S)|(?!\S)(?<=\S) reconnaît exactement ce qu'on veut mais nous voulons remplacer cette
expression compliquée par \Y|. Pour ce faire, nous pouvons créer un module customre :
package customre;
use overload;
sub import {
shift;
die "No argument to customre::import allowed" if @_;
overload::constant 'qr' => \&convert;
}
sub invalid { die "/$_[0]/: invalid escape '\\$_[1]'"}
my %rules = ( '\\' => '\\',
'Y|' => qr/(?=\S)(?<!\S)|(?!\S)(?<=\S)/ );
sub convert {
my $re = shift;
$re =~ s{
\\ ( \\ | Y . )
}
{ $rules{$1} or invalid($re,$1) }sgex;
return $re;
}
Il vous suffit alors d'utiliser use customre pour utiliser cette nouvelle séquence dans les expressions rationnelles
constantes, i.e. celles qui ne nécessitent pas d'interpolation de variables
lors de l'exécution. Tel que documenté dans overload, cette conversion ne fonctionne que sur les parties littérales des
expressions rationnelles. Dans \Y|$re\Y|, la partie variable de cette expression rationnelle doit être convertie
explicitement (mais uniquement si la signification spécifique de \Y| est nécessaire dans $re):
use customre;
$re = <>;
chomp $re;
$re = customre::convert $re;
/\Y|$re\Y|/;
Opérateurs d'expression rationnelle.
Les détails sordides de l'interprétation des chaînes.
pos.
perllocale.
Mastering Regular Expressions (see la page de manuel perlbook) by Jeffrey Friedl.
Paul Gaborit <Paul.Gaborit@enstimac.fr>
Régis Julié <Regis.Julie@cetelem.fr>