Page 1 sur 29
A c c ue i l | Tu to ri al | Te s te ur ( F r) | T es t er ( E n ) | M an P CR E ( E n ) |
Les expressions
rgulires PCRE (Perl
Compatible Regular
Expressions)
Remarques prliminaires
Ce tutoriel est destin aux dveloppeurs
qui matrisent dj, un tant soit peu, la
syntaxe de base des regex et des fonctions
PHP qui les utilisent. Il mettra principalement l'accent sur diffrentes
mthodes d'optimisation. Pour une prsentation dtaille de la syntaxe
lmentaire, il existe de nombreux tutoriaux sur le net notamment regularexpressions.info ou expreg.com.
Les remarques qui vont suivre sur l'implmentation des PCRE par PHP ainsi
que les conseils d'optimisation des motifs valent pour tous les langages qui
utilisent les regex PCRE (Perl, bien sr, mais aussi Python, Java, .NET etc...),
avec des petites variations mineures. Tous ces langages partagent un mme
moteur, de type NFA (Nondeterministic Finite Automation). MySQL, awk et
egrep, par contre, utilisent un autre type de moteur (DFA - Deterministic
Finite Automation) plus simple, moins puissant mais plus rapide pour les cas
(trs) simples, voir simplistes ("Talking about DFA matching is very boring!"
Jeffrey Friedl).
Le support des expressions rgulires dans PHP est assur par deux
bibliothques: POSIX et PCRE.
A premire vue, les fonctions PCRE (preg_match...) ne diffrent pas
beaucoup des fonctions POSIX (ereg....). Le prototype des fonctions ainsi
que la syntaxe des motifs se ressemblent fort. Mais c'est premire vue
seulement!
Les fonctions POSIX telles que ereg ou
ereg_replace sont obsoltes partir de la version
PHP 5.3.0 et leur utilisation dans vos scripts
produira une erreur.
Il ne sera, ds lors, question dans ce tutoriel que d'optimisation des
fonctions PCRE.
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 2 sur 29
Convention typographique
Quantificateur et gourmandise
Rfrences arrires
Les parenthses non capturantes
Remplacement avec "callback"
Les classes de caractres.
Les assertions simples
Choix des options:
i - insensible la casse
s - le point (dot) reconnat le retour ligne
m - multilignes
e - valuation de code php
U - ungreedy: quantificateurs non gourmands
x - Pour commenter les motifs
A, D, S, X - autres options moins courantes
Les assertions positive/ngatives avant/arrires
Les masques conditionnels.
donnes binaires
Optimisation et astuces
Utilisez le dot avec parcimonie
Supprimez les parenthses capturantes inutiles
Optimisez les alternatives.
Scindez vos regex trop complexes
Supprimez les options inutiles
Choisissez les fonctions callback appropries
Ancrez vos motifs
Testez et chronomtrez
man PCRE.
Quelques liens.
Convention typographique
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 3 sur 29
Dans un motif PCRE, on peut insrer une rfrence arrire. C'est dire qu'on
peut capturer quelque-chose dans un texte et l'utiliser plus loin dans le motif
comme rfrence. Une utilisation approprie des rfrences arrires permet
de construire des motifs intelligents.
Exemple, dans la chane :
<i>Texte italique</i> texte normal <b> et texte
gras</b> fin du texte.
Imaginons que nous souhaitions capturer tout ce qui est contenu dans les
balises <b>ou <i>,
La regex suivante est une des solutions possibles preg_match_all('#<
([ib])>(.*?)</\1>#', $txt, $out)
La premire parenthse capturante mmorisera le i ou le b des balises
ouvrantes <b>ou <i> et l'utilisera plus loin dans les balises fermantes
</b>ou </i> grce au motif </\1> o \1, la rfrence arrire, sera gal b
ou i selon ce qui aura t captur plus avant. Rsultat:
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 4 sur 29
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 5 sur 29
[4]=>
Capture [3]=>
[0]=>
[1]=>
[2]=>mer
[3]=>
[4]=>
Capture [4]=>
[0]=>
[1]=>
[2]=>
[3]=>
[4]=>ven
Dmo
La version Perl 5.10 a introduit la possiblilit denepas incrmenter la
numrotation des captures dans unesried'alternatives (Duplicate
Subpattern Numbers). Il est ds lors possiblededonner lemmenumro de
captureaux parenthsecapturantes. A cettefin, lemotif ci dessus s'crira: #
(?|(lun|mar|jeu)|(mer)cre|(ven)dre)di\b#. Remarquez le ?| dela
premireparenthse. Cemotif produira un seul groupedecapture:
Capture [1]=>
[0]=>lun
[1]=>mar
[2]=>mer
[3]=>jeu
[4]=>ven
Il est maintenant trs faciled'utiliser cetteuniquerfrencearrirepour
faireun remplacement par exemple. Voyez dans letesteur cequedonnepar
exemple:
preg_replace('#(?|(lun|mar|jeu)|(mer)cre|(ven)dre)
di\b#', '\1', $texte)
retour menu principal
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 6 sur 29
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 7 sur 29
capturant, celui de $out[2], nous intresse. Par souci de clart, mais surtout
d'efficacit, on peut rendre les premires parenthses non capturantes en
rajoutant simplement ?: aprs la parenthse ouvrante: preg_match_all
('#\b(?:le|les|la|de|des|du)\s(\w+)\b#', $txt, $out) .
Astuce : PHP supporte aussi des expressions
rationnelles compatibles Perl, avec l'extension
PCRE functions. Ces fonctions supportent
des recherches non-gourmandes, des assertions ,
des sous -masques conditionnels et toute une gamme
de fonctionnalits absentes des expressions
rationnelles POSIX.
Le tableau de rsultat $out retournera cette fois ceci:
Matches [0]=>
[0]=> des expressions
[1]=> des recherches
[2]=> des assertions
[3]=> des sous
[4]=> de fonctionnalits
[5]=> des expressions
Capture [1]=>
[0]=> expressions
[1]=> recherches
[2]=> assertions
[3]=> sous
[4]=> fonctionnalits
[5]=> expressions
"So what", vous allez dire! Eh bien, le tableau de sortie n'est pas encombr
d'lments inutiles et il sera ainsi plus facile exploiter, de plus, les indices
des rfrences arrires (backreference) seront plus facile grer et, enfin, la
regex consommera moins de ressources et sera peu prs 10% plus vloce!
Une nano-seconde de gagne par-ci, une micro seconde par-l, muliplies
par le nombre d'accs votre script finiront bien par faire des secondes.
La mme remarque vaut pour une chane alternative du genre #PHP est un
(excellent)? langage de programmation# o le mot excellent est
facultatif. Si vous ne devez pas capturer ce mot, il vaut mieux crire #PHP
est un (?:excellent)? langage de programmation#.
Dmo
retour menu principal
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 8 sur 29
Nous abordons ici une des fonctionnalits les plus utiles des fonctions PCRE.
Utiles car elles permettent d'utiliser, au sein d'une expression rgulire,
n'importe quelle fonction php ou perso.
Il y en a deux: preg_replace() avec l'option e ou bien preg_replace_callback
()
Callback veut simplement dire qu'on demande simplement PHP d'excuter
du code PHP ou une fonction au moment du remplacement. Imaginez, par
exemple, que vous souhaitiez mettre un mot sur deux en majuscule avec la
fonction php strtoupper() et le troisime mot l'envers avec la fonction
strrev() dans le texte suivant:
php supporte aussi des expressions rationnelles
compatibles Perl, avec l'extension PCRE functions.
Ces fonctions supportent des recherches nongourmandes, des assertions, des sous-masques
conditionnels et toute une gamme de
fonctionnalits absentes des expressions POSIX
Le code php suivant:
echo preg_replace('#(\w+)([^\w]*)(\w+)#e','strtoupper
("\1")."\2".strrev("\3")' , $txt);
retournera le texte ceci:
PHP etroppus AUSSI sed EXPRESSIONS sellennoitar
COMPATIBLES lreP, AVEC l'EXTENSION ERCP FUNCTIONS.
seC FONCTIONS tnetroppus DES sehcrehcer NONsednamruog, DES snoitressa, DES suos-MASQUES
slennoitidnoc ET etuot UNE emmag DE
stilannoitcnof ABSENTES sed EXPRESSIONS xisop
Vous percevez certainement la puissance de l'option e. Mais, dans le motif
de remplacement, il ne faut pas se tromper dans les simples et doubles
quotes sinon...
J'avoue que je dois chaque fois rflchir sur ce coup l. C'est ici que la
fonction preg_replace_callback() montre un de ses deux avantages. On
obtient exactement le mme rsultat avec ceci:
function maFonctionTordue($capture){
$txt = strtoupper($capture[1]).$capture
[2].strrev($capture[3]);
return $txt;
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 9 sur 29
}
echo preg_replace_callback('#(\w+)([^\w]*)(\w+)#',
"maFonctionTordue", $txt);
C'est plus clair il me semble. Plus de valses de guillemets! Ici, la fonction
callback "invoque" une fonction perso et lui passe implicitement les captures,
en argument, sous forme de tableau. Ce tableau est rcupr dans la
fonction en question et les diffrentes captures sont alors simplement
accessibles par leur indice.
L'autre avantage de preg_replace_callback() est qu'elle est souvent plus
rapide l'excution. Dans cet exemple, environ 3 fois plus rapide.
Son seul dsavantage mes yeux est qu'on ne peut passer d'autres
arguments que les captures. Si, pour les besoins de l'application, il est
ncessaire de passer d'autres arguments la fonction perso, on peut soit
dclarer ces arguments en GLOBAL au niveau de la fonction callback, soit il
faut en revenir preg_replace() avec l'option e mais cette fois de manire,
disons adapte...
Voici une illustration du premier cas. Les deux arguments passs la
fonction appele sont de simples balises <b> et </b>
function maFonctionTordue2($capture){
global $autreArgument1, $autreArgument2;
$txt = strtoupper($capture[1]).$capture
[2].$autreArgument1.strrev($capture
[3]).$autreArgument2;
return $txt;
}
$autreArgument1 = '<b>';
$autreArgument2 = '</b>';
echo preg_replace_callback('#(\w+)([^\w]*)(\w+)#',
"maFonctionTordue2", $txt);
Si, pour des raisons de cohsion de porte de variable, on rpugne utiliser
GLOBAL, il faut en revenir preg_replace() avec l'option e mais, cette fois,
adapte pour que la manipulation des captures soit simplifie:
function maFonctionTordue2($capture1, $capture2,
$capture3, $autreArgument1, $autreArgument2){
$txt = strtoupper
($capture1).$capture2.$autreArgument1.strrev
($capture3).$autreArgument2;
return $txt;
}
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 10 sur 29
$autreArgument1 = '<b>';
$autreArgument2 = '</b>';
echo preg_replace('#(\w+)([^\w]*)(\w+)
#e','maFonctionTordue2("\1","\2","\3",
"$autreArgument1", "$autreArgument2")' , $txt);
Les captures sont passes comme de simples arguments la fonction perso
afin d'y tre manipules plus simplement. Dans cette manire hybride, on
gagne en clart dans la manipulation des captures tout en ayant la
possibilit de passer des arguments supplmentaires la fonction invoque.
Dans les deux cas, ce code retournera:
PHP etroppus AUSSI sed EXPRESSIONS sellennoitar
COMPATIBLES lreP, AVEC l'EXTENSION ERCP FUNCTIONS.
seC FONCTIONS tnetroppus DES sehcrehcer NONsednamruog, DES snoitressa, DES suos-MASQUES
slennoitidnoc ET etuot UNE emmag DE
stilannoitcnof ABSENTES sed EXPRESSIONS xisop
Astuce: on peut pour galement passer des tableaux en argument comme
ceci:
function maFonctionTordue2($capture1, $capture2,
$capture3, $autreArgument){
$txt = strtoupper
($capture1).$capture2.$autreArgument[0].strrev
($capture3).$autreArgument[1];
return $txt;
}
$autreArgument = array('<b>','</b>');
echo preg_replace('#(\w+)([^\w]*)(\w+)
#e','maFonctionTordue2("\1","\2","\3",
$autreArgument)' , $txt);
retour menu principal
Comme pour les POSIX, il est possible de dfinir des classes de caractres.
La notion de classe de caractres est bien connue. Quelques rappels:
Classe
Description
[a-p]
[^a-p]
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 11 sur 29
Description
Pcre
Commentaire
[:digit:]
dcimal
\d
pas de diffrence
\w
idem \w sauf _
[:word:]
\w
identique
[:alnum:] plus _
[:alpha:]
tout caractre
alphabtique
[a-zA-Z] plus
accentus
[:blank:]
espace et TAB
\s
\s comprend
galement le retour
ligne
\W
[:print:]
[^\000-\037]
pas de diffrence
tout caractre
imprimable (octal
suprieur 037)
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 12 sur 29
\s
[A-Z]
[:lower:]
[a-z]
tout alphabtique
minuscule
\s ne comprend pas
le vertical tab
[a-z] ne comprend
pas les accentus]
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 13 sur 29
ce mot recherch avec des espaces comme par exemple dans ce motif:
preg_match_all('# simples? #', $txt, $out). La concordance (match),
contenue dans le tableau de rsultat $out[0] ne renverra qu'un seul 'simple',
le seul qui ne soit pas suivi de ponctuation:
Voici simplement un simple exemple, trs simple,
mais pas simplet sur les assertions PCRE pourtant
pas si simples!
Rat! Bon, on va alors rajouter la ponctuation dans une classe de caractres
preg_match_all('# simples?[ ,!.;:]#', $txt, $out) pour trouver une
concordance sur les simple ou simples suivis d'une ponctuation
ventuelle. Essayons:
Voici simplement un simple exemple, trs simple,
mais pas simplet sur les assertions PCRE pourtant
pas si simples!
Voil qui est mieux. Mais les concordances (matches) sont encadres soit
par des espaces, soit par de la ponctuation. Pas de problme, on va capturer
ce qui nous intresse avec des parenthses: preg_match_all('#
(simples?)[ ,!.;:]#', $txt, $out). Voici ce que a donne:
Voici simplement un simple exemple, trs simple ,
mais pas simplet sur les assertions PCRE pourtant
pas si simples !
On y est presque. Mais est-ce vraiment ncessaire de consommer de la
mmoire avec des parenthses capturantes (voir la discussion sur les
parenthses non capturantes)? C'est ici que l'utilisation de l'assertion simple
\b va nous tre utile
Essayons ceci: preg_match_all('#\bsimples?\b#', $txt, $out)
Voici simplement un simple exemple, trs simple,
mais pas simplet sur les assertions PCRE pourtant
pas si simples!
Et voil le travail. Cette dernire version est plus rapide que la version
capturante. Et dont le tableau de rsultat $out est plus facile utiliser
puisqu'il comporte deux fois moins d'lments. Pensez aux assertions
simples comme des indicateurs de position dans la chane cible. Cet indice de
position ne pointera pas sur un caractre mais bien entre deux caractres.
Voir ici une dmo de tout ceci. Essayez aussi l'option "timing" dans le
simulateur.
retour menu principal
Les options
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 14 sur 29
i insensible la casse
s le point (dot) reconnat le retour ligne
m multilignes
e valuation de code php dans le groupe de remplacement
U ungreedy, pour rendre les quantificateurs non gourmands
x Pour commenter les motifs
autres A, D, S, X (moins courantes)
Le dot remplace n'importe quel caractre. Mme s'il est souvent prfrable
d'utiliser des classes de caractres ngatives (voir plus loin), l'utilisation du
dot est fort rpandue. Mais seulement, le dot ne prendra pas le retour ligne.
Et un motif utilisant le dot risque de ne pas retourner le rsultat escompt.
Petit exemple pour capturer l'url de ceci: <a
href="http://url.com">lien</a>
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 15 sur 29
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 16 sur 29
000027;Mr;Typhon;TOURNESOL;boulevard
Transgnique;484;
000028;Mr;Archibald;HADDOCK;rue Whiskey;201;
000029;Mr;Marc;DUCHEMIN;clos de la Route;9;
000030;Mr;Philippe;DELAROUTE;chemin d'en
Haut;254;$
Je pourrais utiliser le motif '#^\d+;[^;]+;[^;]+;HADDOCK;([^;]+);([^;]
+);.*#'. L'ancrage ^ me permet d'crire ce motif de manire simple, en
dcomposant tous les champs d'une ligne de fichier, spars par un pointvirgule. En voici le rsultat:
000028;Mr;Chevalier;HADDOCK; rue de Rackam Le Rouge ; 201 ;
000020;Mr;Marc;DUPONT;Avenue Roosens;253;
000021;Mme;Lucienne;MARTINO;rue Joseph Berger;25;
000022;Mr;Aucoin;DUBOIS;avenue des Gents;546;
000024;Mme;Marie;DUPOND;rue de Dinant;58;
000025;Mr;Reporter;TINTIN;clos des Sorbiers;111;
000026;Mr;Lechien;MILOU;rue du Chenil;215;
000027;Mr;Typhon;TOURNESOL;boulevard
Transgnique;484;
000028;Mr;Archibald;HADDOCK;rue Whiskey;201;
000029;Mr;Marc;DUCHEMIN;clos de la Route;9;
000030;Mr;Philippe;DELAROUTE;chemin d'en Haut;254;
La premire ligne a bien t trouve, mais pas l'autre ligne, plus loin dans le
fichier! Tonnerre de Brest!
C'est ici que l'option m intervient.
L'option m (PCRE MULTILINE) demande au moteur regex de considrer
chaque retour ligne comme la fin de chane et le dbut de la suivante. Un
peu comme si ce moteur voyait le fichier plat comme ceci:
^000028;Mr;Chevalier;HADDOCK;rue de Rackam Le
Rouge;201;$
^000020;Mr;Marc;DUPONT;Avenue Roosens;253;$
^000021;Mme;Lucienne;MARTINO;rue Joseph
Berger;25;$
^000022;Mr;Aucoin;DUBOIS;avenue des Gents;546;$
De cette manire, avec l'option m, le motif '#^\d+;[^;]+;[^;]+;HADDOCK;
([^;]+);([^;]+);.*#m' donnera:
000028;Mr;Chevalier;HADDOCK; rue de Rackam Le Rouge ; 201 ;
000020;Mr;Marc;DUPONT;Avenue Roosens;253;
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 17 sur 29
Cette option a t aborde plus haut dans le cadre des fonctions du type
"callback" . Elle n'est utilise que pour la fonction preg_replace(), retour
menu des options
U - Option non gourmande
On a vu plus haut comment rendre un ou plusieurs quantificateur nongourmand dans une regex. L'option U permet de rendre tous les
quantificateurs d'une regex non gourmands. Ces deux motifs sont
parfaitement identiques :
'#<a href="(.*?)">(.*?)</a>#'
'#<a href="(.*)">(.*)</a>#U'
Maintenant, imaginons que vous ayez une loooongue srie de quantificateurs
et que tous doivent tre non-gourmands, sauf un, utilisez l'option globale U
pour l'ensemble du motif, et les "interrupteurs" d'options dcrits plus haut
pour dsactiver cette option sur une portion du motif.
retour menu des options
Option x
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 18 sur 29
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 19 sur 29
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 20 sur 29
Les assertions
On a vu plus haut les assertions simples simples (telle que \b) qui ne
consomment pas de caractres. Vous vous rappelez qu'il convenait de les
voir comme un pointeur qui se trouverait entre deux caractres et pas sur un
caractre. Ils ne cherchent pas une concordance de caractre mais bien de
position. Et bien, les assertions font exactement la mme chose: elles se
placent entre deux caractres et testent les caractres suivants (lookahead)
ou prcdents (lookbehind). Il y en a 4:
Types d'assertion
Motif
(?
<=motif)
(?<!
motif)
Bon, c'est trs bien, mais quoi a peut servir ces trucs l? Imaginons, une
fois encore, que l'on veuille extraire du texte suivant le dterminant de ,
mais uniquement s'il est suivi du mot caractre
L'implmentation des assertions arrires dplace
temporairement le pointeur de position vers
l'arrire, et cherche vrifier l'assertion. Si
le nombre de caractres est diffrent, la position
ne sera pas correcte, et l'assertion chouera. La
combinaison d'assertions arrires avec des sousmasques peut tre particulirement pratique fin
des chanes. Un exemple est donn la fin de
cette section.
Essayons ceci preg_match_all('#\bde\b(?=\scaractres)#', $txt,
$out);. Ce motif placera son pointeur juste aprs le e du mot de (mais
avant le caractre suivant, un espace) et vrifiera si ce qui suit est bien
caractre (avec un espace \s).
Rsultat:
L'implmentation des assertions arrires dplace
temporairement le pointeur de position vers
l'arrire, et cherche vrifier l'assertion. Si
le nombre de caractres est diffrent, la position
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 21 sur 29
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 22 sur 29
On aborde ici une structure un peu plus complexe. Qui fait appel, entreautres, aux assertions (avant ou arrires, positives ou ngatives). De plus, la
doc PHP en franais est assez mal traduite.
Le prototype d'un sous-masque conditionnel (conditional sub-pattern) est le
suivant
(?(condition) masque_si_vrai | masque_sinon).
Il s'agit en fait d'une structure de contrle du type if-then-else. Lorsque nous
avons deux masques possibles, l'valuation de la condition dterminera
l'utilisation de l'un ou de l'autre.
La condition est soit une assertion soit un dcimal se rfrant une
rfrence arrire (capture).
Rien ne vaut un exemple. Nous avons une entte de courriel dans laquelle
on souhaite capturer les diffrents blocs to/from/subject/:
To: destinataire@example.com (commentaire inutile
ne pas capturer)
From: moi@example.net (commentaire encore plus
inutile)
Subject: Ces regex commencent srieusement me
gonfler!
La regex qui tombe sous le sens pourrait ressembler ceci (avec l'option x
pour commenter):
$txt="
To: destinataire@example.com (commentaire inutile)
From: me@example.net (commentaire encore plus
inutile)
Subject: Ces regex commencent srieusement me
gonfler!
";
$motif = '/^(From|To|Subject):\s(.*)/m';
// le mme motif avec commentaires (option x)
$motif='/
^(From|To|Subject)
# dbut de chane (ligne
puisque option m) suivi par From ou To ou Subject
:\s
# suivi par un : et un
espace
(.*)
# suivi par la capture de
tout caractre sauf retour ligne
# option m multiligne et x
pour les commentaires
/xm'
;
preg_match_all($motif, $txt, $out);
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 23 sur 29
Retournera:
To : destinataire@example.com (commentaire inutile)
From : me@example.net (commentaire encore plus inutile)
Subject : Ces regex commencent srieusement me gonfler!
Damned! Il a captur les "commentaires inutiles"! Il faudrait, idalement,
appliquer un masque spcifique pour capturer l'adresse email, et pas plus
loin. Par exemple un masque d'email comme "#\w+@\w+\.[a-z]+#"
capturera bien toute adresse email. Mais il ne capturera rien dans la ligne
Subject car il n'y a aucune adresse email. Comment faire?
Vous aurez devin que c'est ici que les sous-masques conditionnels
interviennent.
Retournera
To : destinataire@example.com (commentaire
inutile)
From : me@example.net (commentaire encore plus
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 24 sur 29
inutile)
Subject : Ces regex commencent srieusement me gonfler!
Une extraction du tableau $out permettra de rassortir les couples To/From
-> adresse email et Subject -> texte sujet.
Dmo
retour menu principal
Astuces et optimisation
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 25 sur 29
On est souvent tent d'utiliser le dot qui prend tout caractre sauf le retour
ligne. Ce qui impose au moteur regex un nombre trs important de
combinaisons possibles (puisque le dot prend tout). On a vu, plus haut,
qu'utiliser une classe de ngation de caractres en lieu et place du dot tait
beaucoup plus efficace. Ne vous en privez pas. En plus, a vous vitera de
tomber dans le pige du dot qui s'arrte un ventuel retour ligne. Enfin, il
n'est pas ncessaire de rendre le quantificateur d'une telle classe nongourmand. Le quantificateur d'une classe de ngation de caractres
s'arrtera toujours au premier caractre ("ngativ") rencontr.
Par contre, si votre motif commence par un .* (trs mauvaise ide a,
surtout s'il n'y a pas d'ancrage ou de lettres fixes en dbut de motif!), le
moteur regex va pdaler dans la choucroute. Voyez:
On recherche une occurrence d'une chane se terminant par mot recherch
dans un texte de deux lignes:
premire ligne suivie par un retour ligne
et mot recherch
Si on cherche le mot cible avec le motif #(.*)mot recherch# impose au
moteur regex un grand nombre d'allers-retours dans la chane avant de
trouver une concordance. Il essayera d'abord toutes les combinaisons
possibles sur la premire ligne. Il ne trouvera rien. Ensuite seulement, il
attaquera la seconde ligne o il trouvera le motif recherch.
Si, maintenant on lui met l'option s #(.*)mot recherch#s, on l'aide en
diminuant sensiblement le nombre de backtracking. Il ira directement
jusqu' la fin de la deuxime ligne. Commencera son backtracking et
trouvera tout de suite le motif recherch.
Consquence: le deuxime motif est peu prs 45% plus rapide.
Dmo
Donc, attention au dot. Mal utilis il pourra planter votre regex!
retour menu astuces
Supprimez les parenthses capturantes inutiles
On a vu plus haut que si vous utilisez les parenthses pour dlimiter une
alternative, rendez-les non capturantes si vous n'avez pas besoin de les
capturer. Ainsi #(le|la|les|un|une)\s(\w+)# qui cherche capturer tous
les mots ((\w+)) sera 30% plus rapide si vous faites #
(?:le|la|les|un|une)\s(\w+)#.
Vous soulagerez le traitement de votre regex et simplifierez galement
l'utilisation des rfrences arrires et le contenu du tableau de rsultats.
retour menu astuces
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 26 sur 29
Tout d'abord, si possible, remplacez les alternatives par des classes. Il est
plus efficace d'crire [akpi] plutt que (?:a|k|p|i). De la mme manire il
vaut mieux faire (?:les?|des?|aux?) que (?:le|les|de|des|au|aux)
Enfin, sachez que lorsque le moteur regex aborde une alternative il prendra
les alternatives une par une en commenant par la premire. Ds qu'il
trouve une concordance, il s'arrtera l et n'analysera pas les autres
alternatives. Si vous commencez l'alternative par le choix le plus probable,
votre moteur regex s'arrtera plus tt. Exemple:
Pour acclrer le traitement de l'expression
rgulire, ordonnez vos alternatives !
Le motif non ordonn #
(?:le|la|les|de|du|des|au|aux|mes|tes|ses|nos|vos) alternatives#
sera plus lent que si vous placiez le choix le plus probable devant: #
(?:vos|la|les|de|du|des|au|aux|mes|tes|ses|nos) alternatives#
Dmo
retour menu astuces
Scindez vos regex trop complexes
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 27 sur 29
'#\bDimanche\b#');
// boucle sur les jours de la semaine et teste si
ce jour se trouve dans le fichier
foreach ($mois as $v){
if (preg_match($v, $txt, $out)){
print_r($out);
break;
}
}
C'est un peu plus compliqu que de le faire en une seule passe, mais
tellement plus efficace! Prs de 10 fois plus rapide!
Et il y a encore certainement moyen d'amliorer la boucle avec une structure
de contrle while() plutt que la structure foreach() - break que j'ai
utilise pour clarifier l'exemple.
retour menu astuces
Supprimez les options inutiles
Si votre motif n'est pas ancr (^ ou $), ou s'il ne commence pas par un
caractre fixe, le moteur de regex va devoir stocker toute une srie d'tats
intermdiaires et devoir faire un srieux nombre de retours sur ces tats
(backtracking). Si vous fixez le dbut ou/et la fin d'une chane vous
diminuerez sensiblement ce backtracking inutile. Vous rappelez-vous
l'exemple du Capitaine Haddock plus haut et sa discussion sur l'utilit des
ancrages?
L'autre manire de faire est d'utiliser l'option S (majuscule) qui lancera une
optimisation du motif avant que le moteur ne commence la recherche
proprement dite. Faites l'essai de cette option dans le simulateur-testeur sur
le motif 2. Vous verrez qu'il fonctionnera peine plus rapidement avec cette
option. Le gain en vitesse d'excution beaucoup plus marqu si vous utilisez
les ancrages.
Dmo avec l'option S
Dmo avec ancrage
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 28 sur 29
Aprs avoir utilis toutes les astuces ci-dessus, mettez-les l'preuve sur un
fichier grandeur nature et chronomtrez! Utilisez ce petit simulateur de
regex en mode timing. Ou bien faites-vous une petite fonction de
chronomtrage et excutez votre regex dans une boucle (quelques centaines
au maximum) pour lisser les rsultats.
$timeStart = microtime(true);
$nbLoops = 200;
while ($nbLoops--) {
/*
* code tester
*/
}
echo number_format(microtime
(true) - $timeStart, 4);
retour menu astuces
retour menu principal
Conclusion
Liens et bibliographie
http://lumadis.be/regex/tuto_pcre.php
14/12/2014
Page 29 sur 29
Tuto et testeur par Jean-Luc Lacroix (regains scs) qui accueillera vos commentaires et/ou critiques avec
plaisir. Surtout si vous avez de bonnes ides pour illustrer l'une ou l'autre fonctionnalit sympa des
PCRE. Merci Nicolas Chambrier pour sa relecture et ses conseils. Contact:
http://lumadis.be/regex/tuto_pcre.php
14/12/2014