Regex (Expressions regulieres)
1. Introduction
1.1 Qu'est-ce qu'une expression reguliere ?
Une expression reguliere (ou regex, abreviation de regular expression) est une sequence de caracteres qui definit un motif de recherche (pattern). Ce motif permet de decrire un ensemble de chaines de caracteres possibles selon des regles precises.
En d'autres termes, une regex est un mini-langage de description de texte. Au lieu de chercher un mot exact, on decrit la forme de ce qu'on cherche.
1.2 A quoi ca sert ?
- Rechercher du texte correspondant a un motif precis dans une chaine ou un fichier
- Valider des formats de donnees (email, telephone, code postal, URL, etc.)
- Extraire des parties specifiques d'un texte (groupes de capture)
- Remplacer du texte selon un motif (rechercher-remplacer avance)
- Decouper une chaine en sous-elements (split)
- Filtrer des lignes dans des fichiers de log, CSV, etc.
1.3 Exemple rapide
Imaginons qu'on veut verifier si une chaine ressemble a une adresse email :
# Au lieu de verifier caractere par caractere...
# On utilise une regex :
/^[\w.+-]+@[\w-]+\.[\w.]+$/
# Ce motif decrit :
# ^ : debut de la chaine
# [\w.+-]+ : un ou plusieurs caracteres alphanumeriques, points, +, -
# @ : le symbole arobase (litteral)
# [\w-]+ : un ou plusieurs caracteres alphanumeriques ou tirets
# \. : un point (litteral, echappe avec \)
# [\w.]+ : un ou plusieurs caracteres alphanumeriques ou points
# $ : fin de la chaine
2. Caracteres speciaux (Metacaracteres)
Les metacaracteres sont des caracteres ayant une signification speciale dans les expressions regulieres. Pour les utiliser comme caracteres litteraux, il faut les echapper avec un antislash (\).
| Caractere | Description | Exemple | Correspond a |
|---|---|---|---|
. |
N'importe quel caractere (sauf \n par defaut) |
a.c |
abc, a1c, a-c, a c |
^ |
Debut de la chaine (ou de la ligne avec flag m) |
^Bonjour |
Bonjour en debut de chaine |
$ |
Fin de la chaine (ou de la ligne avec flag m) |
fin$ |
fin en fin de chaine |
* |
Zero ou plusieurs occurrences du caractere precedent | ab*c |
ac, abc, abbc, abbbc |
+ |
Une ou plusieurs occurrences du caractere precedent | ab+c |
abc, abbc, abbbc (pas ac) |
? |
Zero ou une occurrence du caractere precedent (optionnel) | colou?r |
color, colour |
{n} |
Exactement n occurrences |
a{3} |
aaa |
{n,m} |
Entre n et m occurrences |
a{2,4} |
aa, aaa, aaaa |
[] |
Classe de caracteres (un parmi ceux listes) | [aeiou] |
a, e, i, o, u |
() |
Groupe de capture | (abc)+ |
abc, abcabc |
| |
Alternance (OU logique) | chat|chien |
chat ou chien |
\ |
Echappement (rend le metacaractere litteral) | \. |
Le caractere point . litteral |
\ devant. Par exemple, pour chercher le prix 9.99$, ecrivez 9\.99\$. Les caracteres a echapper sont : . ^ $ * + ? { } [ ] ( ) | \
3. Classes de caracteres
Les classes de caracteres permettent de definir un ensemble de caracteres possibles pour une position donnee. Les raccourcis (\d, \w, \s) simplifient l'ecriture.
3.1 Raccourcis predefinies
| Classe | Equivalent | Description | Exemple | Correspond a |
|---|---|---|---|---|
\d |
[0-9] |
Un chiffre | \d{3} |
123, 007, 999 |
\D |
[^0-9] |
Tout sauf un chiffre | \D+ |
abc, ---, |
\w |
[a-zA-Z0-9_] |
Caractere de mot (lettre, chiffre, underscore) | \w+ |
hello, var_1, test42 |
\W |
[^a-zA-Z0-9_] |
Tout sauf un caractere de mot | \W |
, @, !, - |
\s |
[ \t\n\r\f\v] |
Espace blanc (espace, tab, retour a la ligne) | \s+ |
espaces, tabulations, sauts de ligne |
\S |
[^ \t\n\r\f\v] |
Tout sauf un espace blanc | \S+ |
tout mot/texte sans espace |
3.2 Classes personnalisees avec crochets
| Pattern | Description | Exemple |
|---|---|---|
[abc] |
Un caractere parmi a, b ou c |
[aeiou] correspond a n'importe quelle voyelle |
[a-z] |
Une lettre minuscule de a a z | [a-z]+ correspond a hello |
[A-Z] |
Une lettre majuscule de A a Z | [A-Z]{2} correspond a FR, US |
[0-9] |
Un chiffre de 0 a 9 | [0-9]{5} correspond a 75001 |
[a-zA-Z] |
N'importe quelle lettre (majuscule ou minuscule) | [a-zA-Z]+ correspond a Bonjour |
[a-zA-Z0-9] |
Lettre ou chiffre (alphanumerique) | [a-zA-Z0-9]+ correspond a abc123 |
[^abc] |
N'importe quel caractere sauf a, b ou c (negation) | [^0-9] correspond a tout sauf un chiffre |
[a-z&&[^aeiou]] |
Consonnes uniquement (Java seulement) | Intersection de classes (support variable) |
3.3 Caracteres speciaux dans les crochets
A l'interieur des crochets [], la plupart des metacaracteres perdent leur sens special. Les exceptions :
# Le tiret - definit une plage (sauf en debut ou fin)
[a-z] # plage de a a z
[-az] # litteral : tiret, a ou z (tiret en debut)
[az-] # litteral : a, z ou tiret (tiret en fin)
# Le chapeau ^ en premiere position = negation
[^abc] # tout sauf a, b, c
[a^bc] # a, ^, b ou c (litteral car pas en premiere position)
# Le crochet fermant ] doit etre en premiere position ou echappe
[]abc] # ], a, b ou c
[abc\]] # a, b, c ou ]
# L'antislash \ garde son role d'echappement
[\d\s] # un chiffre ou un espace
[\\] # un antislash litteral
\d est un raccourci pour [0-9], mais attention : dans certaines implementations (ex. Python avec le flag Unicode), \d peut aussi correspondre a des chiffres non-ASCII (chiffres arabes, devanagari, etc.). En cas de doute, utilisez [0-9] pour restreindre aux chiffres ASCII.
4. Quantificateurs
Les quantificateurs indiquent combien de fois l'element precedent peut se repeter.
4.1 Reference des quantificateurs
| Quantificateur | Description | Exemple | Correspond a |
|---|---|---|---|
* |
0 ou plusieurs (zero ou plus) | ab*c |
ac, abc, abbc, abbbc... |
+ |
1 ou plusieurs (un ou plus) | ab+c |
abc, abbc, abbbc... (pas ac) |
? |
0 ou 1 (optionnel) | https? |
http, https |
{n} |
Exactement n fois | \d{4} |
2024, 1234, 0000 |
{n,} |
Au moins n fois (n ou plus) | \d{2,} |
12, 123, 1234567... |
{n,m} |
Entre n et m fois (inclus) | \d{2,4} |
12, 123, 1234 |
{0,m} |
Au maximum m fois | a{0,3} |
(vide), a, aa, aaa |
4.2 Greedy vs Lazy (Gourmand vs Paresseux)
Par defaut, les quantificateurs sont gourmands (greedy) : ils capturent le maximum de caracteres possible. En ajoutant ? apres le quantificateur, il devient paresseux (lazy) et capture le minimum.
| Greedy (gourmand) | Lazy (paresseux) | Description |
|---|---|---|
* |
*? |
0 ou plus, mais le moins possible |
+ |
+? |
1 ou plus, mais le moins possible |
? |
?? |
0 ou 1, mais prefere 0 |
{n,m} |
{n,m}? |
Entre n et m, mais le moins possible |
# Exemple concret : extraire le contenu d'une balise HTML
# Texte : <b>gras</b> et <b>aussi gras</b>
# GREEDY (gourmand) - <b>.*</b>
# Resultat : <b>gras</b> et <b>aussi gras</b>
# (capture TOUT entre le premier <b> et le DERNIER </b>)
# LAZY (paresseux) - <b>.*?</b>
# Resultat : <b>gras</b> puis <b>aussi gras</b>
# (capture le MINIMUM entre chaque paire <b>...</b>)
*?, +?) quand vous voulez capturer la correspondance la plus courte possible.
5. Ancres et limites
Les ancres ne correspondent a aucun caractere : elles indiquent une position dans la chaine.
5.1 Ancres de base
| Ancre | Description | Exemple | Explication |
|---|---|---|---|
^ |
Debut de la chaine (ou de la ligne avec flag m) |
^Bonjour |
Correspond seulement si "Bonjour" est au debut |
$ |
Fin de la chaine (ou de la ligne avec flag m) |
monde$ |
Correspond seulement si "monde" est a la fin |
\b |
Limite de mot (entre un \w et un \W) |
\bchat\b |
Correspond a "chat" mais pas a "chaton" ni "rachat" |
\B |
Pas une limite de mot (inverse de \b) |
\Bchat\B |
Correspond a "chat" dans "rachats" mais pas au mot "chat" seul |
# Exemples pratiques avec \b (limite de mot)
\bweb\b # "web" comme mot complet
# OUI : "le web est", "web design"
# NON : "cobweb", "website", "webinar"
\bpre # mots commencant par "pre"
# OUI : "premier", "prevenir", "precis"
tion\b # mots finissant par "tion"
# OUI : "action", "station", "creation"
# Exemples avec ^ et $
^$ # ligne vide (rien entre le debut et la fin)
^.+$ # ligne non vide (au moins un caractere)
^\s*$ # ligne vide ou contenant uniquement des espaces
5.2 Lookahead (assertion avant)
Les lookahead verifient ce qui suit la position actuelle sans le consommer (la position ne bouge pas).
| Syntaxe | Nom | Description | Exemple | Resultat |
|---|---|---|---|---|
(?=...) |
Lookahead positif | Ce qui suit doit correspondre | \d+(?= euros) |
Capture 100 dans "100 euros" mais pas dans "100 dollars" |
(?!...) |
Lookahead negatif | Ce qui suit ne doit PAS correspondre | \d+(?! euros) |
Capture 100 dans "100 dollars" mais pas dans "100 euros" |
5.3 Lookbehind (assertion arriere)
Les lookbehind verifient ce qui precede la position actuelle sans le consommer.
| Syntaxe | Nom | Description | Exemple | Resultat |
|---|---|---|---|---|
(?<=...) |
Lookbehind positif | Ce qui precede doit correspondre | (?<=\$)\d+ |
Capture 50 dans "$50" mais pas dans "50" |
(?<!...) |
Lookbehind negatif | Ce qui precede ne doit PAS correspondre | (?<!\$)\d+ |
Capture 50 dans "50 items" mais pas dans "$50" |
# Exemples pratiques de lookahead / lookbehind
# Trouver des montants en euros (nombre suivi de "EUR" ou "euros")
\d+(?:\.\d{2})?(?=\s*(?:EUR|euros?))
# Correspond a : "150.00 EUR", "42 euros", "9.99 euro"
# Extraire le prix sans le symbole dollar
(?<=\$)\d+(?:\.\d{2})?
# Dans "$19.99 et $5.00" : capture "19.99" et "5.00"
# Mot suivi d'un point mais sans capturer le point
\w+(?=\.)
# Dans "fin. suite" : capture "fin"
# Mot de passe : verifier qu'il contient au moins un chiffre et une majuscule
# (utilisation de lookahead multiples)
^(?=.*[A-Z])(?=.*\d).{8,}$
# Valide : "Passw0rd", "Test1234"
# Invalide : "password", "12345678"
- JavaScript : lookbehind supporte depuis ES2018 (longueur variable autorisee)
- PHP (PCRE) : lookbehind doit avoir une longueur fixe (pas de
*ou+) - Python : lookbehind doit avoir une longueur fixe
- Java : lookbehind a longueur variable limitee (pas de quantificateurs illimites)
6. Groupes et captures
Les groupes permettent de regrouper des elements, d'appliquer des quantificateurs sur des sous-expressions et d'extraire des parties du texte.
6.1 Types de groupes
| Syntaxe | Nom | Description | Exemple |
|---|---|---|---|
(...) |
Groupe de capture | Capture le contenu pour reutilisation | (ab)+ capture "ab", "abab" |
(?:...) |
Groupe non-capturant | Regroupe sans capturer (meilleure performance) | (?:ab)+ meme effet sans capture |
(?P<nom>...) |
Groupe nomme (PHP/Python) | Capture avec un nom (plus lisible) | (?P<annee>\d{4}) |
(?<nom>...) |
Groupe nomme (JS/Java/.NET) | Capture avec un nom (syntaxe alternative) | (?<year>\d{4}) |
(?=...) |
Lookahead positif | Verifie sans consommer (voir section 5) | foo(?=bar) |
(?!...) |
Lookahead negatif | Verifie l'absence sans consommer | foo(?!bar) |
(?<=...) |
Lookbehind positif | Verifie ce qui precede sans consommer | (?<=@)\w+ |
(?<!...) |
Lookbehind negatif | Verifie l'absence en arriere | (?<!un)chat |
6.2 References arriere (Back-references)
Les back-references permettent de faire reference a un groupe deja capture plus tot dans la meme regex. Utile pour trouver des repetitions ou des correspondances symetriques.
| Syntaxe | Description | Exemple | Correspond a |
|---|---|---|---|
\1, \2, ... |
Reference au groupe N (par numero) | (mot)\s+\1 |
"mot mot" (mot repete) |
\k<nom> |
Reference a un groupe nomme | (?<tag>\w+).*\k<tag> |
Balises appairees |
# Trouver les mots repetes (doublons)
\b(\w+)\s+\1\b
# Trouve : "le le", "est est", "the the"
# Texte : "Il est est parti" -> correspond a "est est"
# Trouver les balises HTML ouvrantes et fermantes appairees
<(\w+)[^>]*>.*?</\1>
# Trouve : <div>contenu</div>, <p>texte</p>
# Ne trouve PAS : <div>contenu</p> (balises differentes)
# Extraire une date au format JJ/MM/AAAA en groupes
(\d{2})/(\d{2})/(\d{4})
# Sur "25/12/2024" :
# Groupe 1 (\1) = "25" (jour)
# Groupe 2 (\2) = "12" (mois)
# Groupe 3 (\3) = "2024" (annee)
# Reformater une date de JJ/MM/AAAA vers AAAA-MM-JJ (en remplacement)
# Recherche : (\d{2})/(\d{2})/(\d{4})
# Remplacement : $3-$2-$1 (ou \3-\2-\1 selon le langage)
# "25/12/2024" -> "2024-12-25"
# Groupe nomme : extraire des parties d'une URL
(?P<protocole>https?):\/\/(?P<domaine>[\w.-]+)(?::(?P<port>\d+))?(?P<chemin>\/\S*)?
# Sur "https://example.com:8080/page?id=1" :
# protocole = "https"
# domaine = "example.com"
# port = "8080"
# chemin = "/page?id=1"
(?:...) quand vous avez besoin de regrouper pour appliquer un quantificateur mais que vous n'avez pas besoin de capturer le contenu. Cela ameliore legerement les performances et garde la numerotation des groupes propre.
7. Drapeaux (Flags)
Les drapeaux modifient le comportement global du moteur regex. Ils s'ajoutent generalement apres le delimiteur de fin du pattern.
| Flag | Nom | Description | Exemple |
|---|---|---|---|
g |
Global | Trouver toutes les correspondances (pas seulement la premiere) | /abc/g trouve tous les "abc" dans le texte |
i |
Insensible a la casse | Ignore la difference majuscules/minuscules | /bonjour/i trouve "Bonjour", "BONJOUR", "bonjour" |
m |
Multiligne | ^ et $ correspondent au debut/fin de chaque ligne (pas seulement la chaine entiere) |
/^ligne/m trouve "ligne" au debut de chaque ligne |
s |
Dotall (single line) | Le point . correspond aussi aux sauts de ligne \n |
/debut.*fin/s traverse les lignes |
u |
Unicode | Traite le pattern et le texte en UTF-8 / mode Unicode complet | /\w+/u comprend les caracteres accentues |
x |
Verbose / etendu | Ignore les espaces et autorise les commentaires # dans le pattern |
Permet d'ecrire des regex lisibles sur plusieurs lignes |
# Syntaxe des drapeaux selon le langage
# JavaScript : /pattern/flags
let regex = /bonjour/gi; // global + insensible a la casse
# PHP : "delimiteur pattern delimiteur flags"
$regex = '/bonjour/giu'; // global + insensible + unicode
# Python : re.compile(pattern, flags)
import re
regex = re.compile(r'bonjour', re.IGNORECASE | re.MULTILINE)
# Exemple avec le flag x (verbose) en PHP/Python :
$regex = '/
^ # debut de la chaine
[\w.+-]+ # partie locale de l email
@ # arobase
[\w-]+ # nom de domaine
\. # point
[a-zA-Z]{2,} # extension (TLD)
$ # fin de la chaine
/x';
# Equivalent compact : /^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$/
x (verbose/etendu) est extremement utile pour les regex complexes. Il permet d'ajouter des espaces et des commentaires, rendant le pattern beaucoup plus lisible et maintenable. Utilisez-le systematiquement pour les regex de plus de 30 caracteres.
8. Exemples pratiques courants
Voici une collection complete de patterns regex pour les cas d'utilisation les plus frequents. Chaque pattern est fourni avec des exemples de chaines valides et invalides.
8.1 Adresse email
# Validation basique d'email
^[\w.+-]+@[\w-]+\.[\w.]+$
# Validation plus stricte (RFC 5322 simplifiee)
^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$
# Exemples valides : user@example.com, prenom.nom@societe.fr, test+tag@mail.co.uk
# Exemples invalides : @example.com, user@, user@.com, user@com
8.2 Numero de telephone francais
# Format national : 01 23 45 67 89 ou 0123456789
^0[1-9](?:[\s.-]?\d{2}){4}$
# Format international : +33 1 23 45 67 89 ou +33123456789
^(?:\+33|0033|0)[1-9](?:[\s.-]?\d{2}){4}$
# Telephone mobile uniquement (06 et 07)
^(?:\+33|0033|0)[67](?:[\s.-]?\d{2}){4}$
# Exemples valides : 01 23 45 67 89, 06.12.34.56.78, +33 6 12 34 56 78, 0612345678
# Exemples invalides : 00 12 34 56 78, 1234567890, +33 0 12 34 56 78
8.3 Code postal francais
# Code postal francais (5 chiffres, departements 01 a 98 + DOM-TOM)
^(?:0[1-9]|[1-8]\d|9[0-8])\d{3}$
# Avec les DOM-TOM (97x, 98x)
^(?:0[1-9]|[1-8]\d|9[0-8]|97[1-6]|98[4-8])\d{2,3}$
# Version simple (5 chiffres commencant par 01-98)
^\d{5}$
# Exemples valides : 75001, 13100, 97400, 69001
# Exemples invalides : 00123, 99999, 7500, 750012
8.4 Adresse IP (IPv4)
# IPv4 stricte (0-255 par octet)
^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$
# IPv4 simple (moins stricte mais plus lisible)
^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$
# IPv4 avec masque CIDR
^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\/(?:3[0-2]|[12]?\d)$
# Exemples valides : 192.168.1.1, 10.0.0.0, 255.255.255.0, 172.16.0.1/24
# Exemples invalides : 256.1.1.1, 192.168.1, 10.0.0.0.1
8.5 URL
# URL HTTP/HTTPS
^https?:\/\/(?:[\w-]+\.)+[a-zA-Z]{2,}(?::\d{1,5})?(?:\/[^\s]*)?$
# URL plus complete (avec parametres et ancre)
^https?:\/\/(?:www\.)?[\w-]+(?:\.[\w-]+)+(?::\d{1,5})?(?:\/[\w.~:/?#\[\]@!$&'()*+,;=-]*)?$
# Exemples valides : https://example.com, http://www.site.fr/page?id=1#section
# Exemples invalides : ftp://server.com, example.com (manque le protocole)
8.6 Date au format JJ/MM/AAAA
# Date JJ/MM/AAAA (validation basique)
^(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/\d{4}$
# Date avec separateur flexible (/, - ou .)
^(0[1-9]|[12]\d|3[01])[\/\-\.](0[1-9]|1[0-2])[\/\-\.]\d{4}$
# Date AAAA-MM-JJ (format ISO 8601)
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
# Exemples valides : 25/12/2024, 01-01-2000, 2024-06-15
# Exemples invalides : 32/01/2024, 00/13/2024, 2024/1/1
8.7 Mot de passe fort
# Mot de passe fort : 8+ caracteres, 1 majuscule, 1 minuscule, 1 chiffre
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$
# Mot de passe tres fort : 12+ car., majuscule, minuscule, chiffre, special
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]).{12,}$
# Mot de passe sans espaces, 8-64 caracteres
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])[^\s]{8,64}$
# Exemples valides : "Passw0rd!", "MonSuperMDP2024!", "C0mpl3x&Fort"
# Exemples invalides : "password", "12345678", "abcABC", "Short1!"
8.8 Nom de fichier
# Nom de fichier valide (lettres, chiffres, tirets, underscores, point pour extension)
^[\w\-]+\.\w{1,10}$
# Nom de fichier avec chemin (Unix)
^(?:\/[\w.\-]+)+$
# Nom de fichier avec chemin (Windows)
^[a-zA-Z]:\\(?:[\w.\-\s]+\\)*[\w.\-\s]+$
# Extension specifique (images)
^[\w\-]+\.(?:jpg|jpeg|png|gif|svg|webp|bmp)$
# Extension specifique (documents)
^[\w\-]+\.(?:pdf|doc|docx|xls|xlsx|ppt|pptx|txt|csv)$
# Exemples valides : rapport-2024.pdf, image_01.png, C:\Users\docs\file.txt
# Exemples invalides : fichier, ../hack.php, fichier<>.txt
8.9 Adresse MAC
# Adresse MAC (format XX:XX:XX:XX:XX:XX)
^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$
# Adresse MAC (format XX-XX-XX-XX-XX-XX)
^([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}$
# Les deux formats
^([0-9A-Fa-f]{2}[:\-]){5}[0-9A-Fa-f]{2}$
# Format Cisco (XXXX.XXXX.XXXX)
^([0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4}$
# Exemples valides : 00:1A:2B:3C:4D:5E, 00-1a-2b-3c-4d-5e, 001A.2B3C.4D5E
# Exemples invalides : 00:1A:2B:3C:4D, GG:HH:II:JJ:KK:LL
8.10 Code IBAN
# IBAN generique (2 lettres pays + 2 chiffres controle + 10 a 30 alphanumeriques)
^[A-Z]{2}\d{2}[A-Z0-9]{10,30}$
# IBAN francais (27 caracteres : FR + 2 chiffres + 23 alphanumeriques)
^FR\d{2}[\dA-Z]{23}$
# IBAN francais avec espaces (format lisible)
^FR\d{2}(?:\s?\d{4}){5}\s?\d{3}$
# Exemples valides : FR7630001007941234567890185, FR76 3000 1007 9412 3456 7890 185
# Exemples invalides : FR123, XXFR7630001007941234567890185
8.11 Balises HTML
# Trouver une balise HTML ouvrante
<([a-zA-Z][\w-]*)(?:\s+[^>]*)?>
# Trouver une balise HTML fermante
<\/([a-zA-Z][\w-]*)>
# Trouver une balise complete avec son contenu (non-imbrique)
<(\w+)(?:\s+[^>]*)?>(.*?)<\/\1>
# Balises auto-fermantes
<(\w+)(?:\s+[^>]*)?\s*\/>
# Extraire les attributs d'une balise
(\w+)=["']([^"']*)["']
# Supprimer toutes les balises HTML (texte brut)
<[^>]+>
# Exemples :
# <div class="container"> -> balise ouvrante, classe="container"
# </div> -> balise fermante
# <img src="photo.jpg" /> -> balise auto-fermante
# <p>Texte</p> -> balise complete
8.12 Espaces multiples et nettoyage
# Remplacer les espaces multiples par un seul espace
\s{2,}
# Remplacement : " " (un seul espace)
# "Hello World" -> "Hello World"
# Supprimer les espaces en debut et fin de chaine (trim)
^\s+|\s+$
# Remplacement : "" (chaine vide)
# " Hello World " -> "Hello World"
# Supprimer les lignes vides
^\s*$\n?
# Remplacement : "" (avec flag m pour multiligne)
# Supprimer les espaces autour des virgules
\s*,\s*
# Remplacement : ", "
# "a ,b , c, d" -> "a, b, c, d"
# Supprimer les espaces en debut de chaque ligne
^[ \t]+
# Avec flag m (multiligne)
8.13 Commentaires de code
# Commentaires monoligne C/JS/PHP (//)
\/\/.*$
# Commentaires multiligne C/JS/PHP (/* ... */)
\/\*[\s\S]*?\*\/
# Commentaires monoligne Python/Bash (#)
#.*$
# Commentaires HTML (<!-- ... -->)
<!--[\s\S]*?-->
# Tous les commentaires JS/PHP (mono + multi ligne)
\/\/.*$|\/\*[\s\S]*?\*\/
# Exemples :
# // Ceci est un commentaire -> correspond
# /* Ceci est
# un commentaire */ -> correspond
# # commentaire bash -> correspond
# <!-- commentaire HTML --> -> correspond
8.14 Nombres et montants
# Nombre entier (positif)
^\d+$
# Nombre entier (positif ou negatif)
^-?\d+$
# Nombre decimal (avec point ou virgule)
^-?\d+(?:[.,]\d+)?$
# Montant en euros (2 decimales obligatoires)
^\d+(?:\s?\d{3})*[.,]\d{2}$
# Montant avec separateur de milliers
^\d{1,3}(?:\s\d{3})*(?:[.,]\d{2})?$
# Nombre hexadecimal
^(?:0x)?[0-9A-Fa-f]+$
# Exemples valides : 42, -17, 3.14, 1 234,56, 0xFF
# Exemples invalides : 12.34.56, --5, 12,345.67.89
8.15 Autres patterns utiles
| Usage | Pattern | Exemple valide |
|---|---|---|
| Numero de securite sociale FR | ^[12]\d{2}(?:0[1-9]|1[0-2])\d{2}\d{3}\d{3}\d{2}$ |
185037512300145 |
| SIRET (14 chiffres) | ^\d{14}$ |
12345678901234 |
| SIREN (9 chiffres) | ^\d{9}$ |
123456789 |
| Plaque d'immatriculation FR (SIV) | ^[A-Z]{2}-\d{3}-[A-Z]{2}$ |
AB-123-CD |
| UUID v4 | ^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ |
550e8400-e29b-41d4-a716-446655440000 |
| Couleur hexadecimale CSS | ^#(?:[0-9A-Fa-f]{3}){1,2}$ |
#FF5733, #abc |
| Version semantique (SemVer) | ^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$ |
2.1.0, 1.0.0-beta.1 |
| Nom de variable (JS/PHP) | ^[a-zA-Z_$][\w$]*$ |
maVariable, _count, $prix |
| Balise d'ouverture HTML | <([a-z][\w-]*)(?:\s[^>]*)?\s*> |
<div class="test"> |
| Hashtag (reseaux sociaux) | #[a-zA-Z\u00C0-\u024F][\w\u00C0-\u024F]* |
#Regex, #DevWeb |
9. Regex en JavaScript
En JavaScript, les regex sont des objets de premiere classe. On peut les creer avec la notation litterale /pattern/flags ou via le constructeur new RegExp().
9.1 Creer une regex
// Notation litterale (recommandee pour les patterns statiques)
const regex1 = /bonjour/gi;
// Constructeur RegExp (utile quand le pattern est dynamique)
const motRecherche = "bonjour";
const regex2 = new RegExp(motRecherche, "gi");
// Attention : avec le constructeur, il faut doubler les antislash
const regexChiffres = new RegExp("\\d+"); // equivalent de /\d+/
9.2 Methodes principales
| Methode | Description | Retourne |
|---|---|---|
regex.test(str) |
Teste si le pattern correspond | true ou false |
regex.exec(str) |
Execute la regex et retourne les details de la correspondance | Array ou null |
str.match(regex) |
Trouve les correspondances | Array ou null |
str.matchAll(regex) |
Iterateur sur toutes les correspondances (avec groupes) | Iterator |
str.replace(regex, remplacement) |
Remplace les correspondances | Nouvelle chaine |
str.replaceAll(regex, remplacement) |
Remplace toutes les correspondances (flag g requis) |
Nouvelle chaine |
str.search(regex) |
Trouve l'index de la premiere correspondance | Index (number) ou -1 |
str.split(regex) |
Decoupe la chaine selon le pattern | Array de sous-chaines |
9.3 Exemples pratiques
// ===== test() : validation rapide =====
const emailRegex = /^[\w.+-]+@[\w-]+\.[\w.]+$/;
emailRegex.test("user@example.com"); // true
emailRegex.test("invalide@"); // false
// Validation de mot de passe fort
const mdpRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$/;
mdpRegex.test("MonMdp2024!"); // true
mdpRegex.test("faible"); // false
// ===== match() : trouver des correspondances =====
const texte = "Appelez le 01 23 45 67 89 ou le 06 78 90 12 34";
const telRegex = /0[1-9](?:[\s.-]?\d{2}){4}/g;
texte.match(telRegex);
// Resultat : ["01 23 45 67 89", "06 78 90 12 34"]
// ===== matchAll() : correspondances avec groupes de capture =====
const html = '<a href="page1.html">Lien 1</a> et <a href="page2.html">Lien 2</a>';
const lienRegex = /href="([^"]+)">([^<]+)/g;
for (const match of html.matchAll(lienRegex)) {
console.log(`URL: ${match[1]}, Texte: ${match[2]}`);
}
// URL: page1.html, Texte: Lien 1
// URL: page2.html, Texte: Lien 2
// ===== replace() : remplacement avec captures =====
// Reformater une date de JJ/MM/AAAA vers AAAA-MM-JJ
const date = "25/12/2024";
date.replace(/(\d{2})\/(\d{2})\/(\d{4})/, "$3-$2-$1");
// Resultat : "2024-12-25"
// Remplacer avec une fonction de callback
const prix = "Le produit coute 19.99 EUR et l'autre 5.50 EUR";
prix.replace(/(\d+\.\d{2})\s*EUR/g, (match, montant) => {
return `${(parseFloat(montant) * 1.20).toFixed(2)} EUR TTC`;
});
// Resultat : "Le produit coute 23.99 EUR TTC et l'autre 6.60 EUR TTC"
// Nettoyer les espaces multiples
" Hello World ".replace(/\s+/g, " ").trim();
// Resultat : "Hello World"
// ===== exec() : iteration detaillee =====
const codeRegex = /(?<code>\d{2})(?<ville>\d{3})/g;
const cp = "75001 13100 69003";
let result;
while ((result = codeRegex.exec(cp)) !== null) {
console.log(`Dept: ${result.groups.code}, Ville: ${result.groups.ville}`);
}
// Dept: 75, Ville: 001
// Dept: 13, Ville: 100
// Dept: 69, Ville: 003
// ===== split() : decoupage avec regex =====
"pomme, banane; cerise orange".split(/[,;\s]+/);
// Resultat : ["pomme", "banane", "cerise", "orange"]
"2024-01-15T14:30:00".split(/[-T:]/);
// Resultat : ["2024", "01", "15", "14", "30", "00"]
// ===== search() : trouver la position =====
"Bonjour le monde".search(/monde/);
// Resultat : 12 (position du debut de "monde")
"Pas de chiffre ici".search(/\d/);
// Resultat : -1 (pas trouve)
exec() et le flag g : la regex garde un etat interne (lastIndex). Si vous reutilisez la meme regex, pensez a reinitialiser regex.lastIndex = 0 ou utilisez matchAll() qui est plus previsible.
10. Regex en PHP
PHP utilise les fonctions PCRE (Perl Compatible Regular Expressions) via les fonctions preg_*. Les patterns doivent etre entoures de delimiteurs (generalement /).
10.1 Fonctions principales
| Fonction | Description | Retourne |
|---|---|---|
preg_match($pattern, $subject, &$matches) |
Cherche la premiere correspondance | 1 (trouve), 0 (pas trouve), false (erreur) |
preg_match_all($pattern, $subject, &$matches) |
Cherche toutes les correspondances | Nombre de correspondances ou false |
preg_replace($pattern, $replacement, $subject) |
Remplace les correspondances | Chaine modifiee ou null |
preg_replace_callback($pattern, $callback, $subject) |
Remplace via une fonction de callback | Chaine modifiee ou null |
preg_split($pattern, $subject) |
Decoupe la chaine selon le pattern | Array de sous-chaines ou false |
preg_quote($str, $delimiter) |
Echappe les caracteres speciaux regex | Chaine echappee |
preg_grep($pattern, $array) |
Filtre un tableau selon le pattern | Array des elements correspondants |
10.2 Exemples pratiques
// ===== preg_match() : premiere correspondance =====
// Valider un email
$email = "user@example.com";
if (preg_match('/^[\w.+-]+@[\w-]+\.[\w.]+$/', $email)) {
echo "Email valide";
}
// Extraire des groupes de capture
$date = "25/12/2024";
if (preg_match('/^(\d{2})\/(\d{2})\/(\d{4})$/', $date, $matches)) {
echo "Jour: {$matches[1]}, Mois: {$matches[2]}, Annee: {$matches[3]}";
// Jour: 25, Mois: 12, Annee: 2024
}
// Groupes nommes
$url = "https://www.example.com:8080/page";
$pattern = '/^(?P<protocole>https?):\/\/(?P<domaine>[\w.-]+)(?::(?P<port>\d+))?/';
if (preg_match($pattern, $url, $matches)) {
echo "Protocole: {$matches['protocole']}"; // https
echo "Domaine: {$matches['domaine']}"; // www.example.com
echo "Port: {$matches['port']}"; // 8080
}
// ===== preg_match_all() : toutes les correspondances =====
// Trouver tous les numeros de telephone dans un texte
$texte = "Appelez le 01 23 45 67 89 ou le 06-78-90-12-34";
$nb = preg_match_all('/0[1-9](?:[\s.-]?\d{2}){4}/', $texte, $matches);
echo "$nb numeros trouves : " . implode(", ", $matches[0]);
// 2 numeros trouves : 01 23 45 67 89, 06-78-90-12-34
// Extraire tous les liens d'un document HTML
$html = '<a href="page1.html">Lien 1</a> <a href="page2.html">Lien 2</a>';
preg_match_all('/href="([^"]+)"/', $html, $matches);
// $matches[0] = ['href="page1.html"', 'href="page2.html"']
// $matches[1] = ['page1.html', 'page2.html']
// Extraire les paires cle=valeur
$config = "nom=Jean age=30 ville=Paris";
preg_match_all('/(\w+)=(\w+)/', $config, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
echo "{$m[1]} => {$m[2]}\n";
}
// nom => Jean
// age => 30
// ville => Paris
// ===== preg_replace() : remplacement =====
// Reformater une date
$date = "25/12/2024";
$resultat = preg_replace('/(\d{2})\/(\d{2})\/(\d{4})/', '$3-$2-$1', $date);
// "2024-12-25"
// Nettoyer les espaces multiples
$texte = " Trop d'espaces ici ";
$propre = trim(preg_replace('/\s+/', ' ', $texte));
// "Trop d'espaces ici"
// Supprimer les balises HTML
$html = "<p>Texte <strong>en gras</strong></p>";
$texteBrut = preg_replace('/<[^>]+>/', '', $html);
// "Texte en gras"
// Remplacement avec tableau de patterns
$texte = "Lundi 01/01 et Mardi 02/01";
$resultat = preg_replace(
['/Lundi/', '/Mardi/', '/(\d{2})\/(\d{2})/'],
['Monday', 'Tuesday', '$2-$1'],
$texte
);
// "Monday 01-01 et Tuesday 01-02"
// ===== preg_replace_callback() : remplacement dynamique =====
// Convertir des montants EUR -> USD (avec calcul)
$texte = "Prix : 100.00 EUR et 250.50 EUR";
$resultat = preg_replace_callback(
'/(\d+\.\d{2})\s*EUR/',
function ($matches) {
$usd = round(floatval($matches[1]) * 1.10, 2);
return "$usd USD";
},
$texte
);
// "Prix : 110.00 USD et 275.55 USD"
// Transformer le premier caractere de chaque mot en majuscule
$texte = "bonjour le monde";
$resultat = preg_replace_callback('/\b\w/', function ($m) {
return strtoupper($m[0]);
}, $texte);
// "Bonjour Le Monde"
// ===== preg_split() : decoupage =====
// Decouper par plusieurs separateurs
$chaine = "pomme, banane; cerise orange|kiwi";
$fruits = preg_split('/[,;\s|]+/', $chaine);
// ["pomme", "banane", "cerise", "orange", "kiwi"]
// Decouper un CSV avec guillemets
$ligne = '"Nom","Prenom","Age"';
$colonnes = preg_split('/","/', trim($ligne, '"'));
// ["Nom", "Prenom", "Age"]
// ===== preg_grep() : filtrer un tableau =====
$fichiers = ["index.php", "style.css", "app.js", "config.php", "logo.png"];
// Garder uniquement les fichiers PHP
$phpFiles = preg_grep('/\.php$/', $fichiers);
// ["index.php", "config.php"]
// Exclure les fichiers PHP (flag PREG_GREP_INVERT)
$autresFichiers = preg_grep('/\.php$/', $fichiers, PREG_GREP_INVERT);
// ["style.css", "app.js", "logo.png"]
// ===== preg_quote() : echapper pour insertion dans un pattern =====
$recherche = "prix (en $)";
$pattern = '/\b' . preg_quote($recherche, '/') . '\b/';
// Le pattern resultant echappe les ( ) et $ correctement
u (Unicode/UTF-8) dans vos patterns si vous travaillez avec du texte francais ou multilingue : '/[\w]+/u'. Sans ce flag, les caracteres accentues ne seront pas reconnus par \w.
11. Regex en Bash (grep, sed, awk)
En ligne de commande Linux, les outils grep, sed et awk utilisent des expressions regulieres pour rechercher, filtrer et transformer du texte. Il existe deux types de regex : BRE (Basic) et ERE (Extended).
11.1 BRE vs ERE
| Caractere | BRE (Basic) | ERE (Extended) | Commande |
|---|---|---|---|
+ |
\+ |
+ |
BRE = grep, ERE = grep -E / egrep |
? |
\? |
? |
BRE = sed, ERE = sed -E |
{n,m} |
\{n,m\} |
{n,m} |
ERE = awk (utilise ERE par defaut) |
(...) |
\(...\) |
(...) |
PCRE = grep -P (si disponible) |
| |
\| |
| |
11.2 grep - Recherche dans les fichiers
# Recherche basique (BRE)
grep "motif" fichier.txt
# Recherche avec regex etendue (ERE) - recommande
grep -E "motif" fichier.txt
# Recherche avec PCRE (Perl Compatible) - le plus puissant
grep -P "motif" fichier.txt
# Options utiles
grep -i "motif" fichier.txt # Insensible a la casse
grep -n "motif" fichier.txt # Afficher les numeros de ligne
grep -c "motif" fichier.txt # Compter les correspondances
grep -l "motif" *.txt # Lister les fichiers correspondants
grep -r "motif" /chemin/dossier/ # Recherche recursive
grep -v "motif" fichier.txt # Inverser (lignes ne correspondant PAS)
grep -w "mot" fichier.txt # Mot entier uniquement
grep -o "motif" fichier.txt # Afficher uniquement la partie correspondante
grep -A 3 "motif" fichier.txt # 3 lignes apres chaque correspondance
grep -B 2 "motif" fichier.txt # 2 lignes avant chaque correspondance
grep -C 2 "motif" fichier.txt # 2 lignes avant ET apres
# Exemples concrets
# Trouver les lignes contenant une adresse IP
grep -E "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b" access.log
# Trouver les erreurs 404 et 500 dans les logs Apache
grep -E "\" (404|500) " /var/log/apache2/access.log
# Lister les fichiers PHP contenant "password" (recursive)
grep -rl "password" /var/www/ --include="*.php"
# Trouver les TODO/FIXME dans le code
grep -rn -E "(TODO|FIXME|HACK|XXX)" /var/www/projet/ --include="*.{php,js}"
# Compter les lignes vides dans un fichier
grep -c "^$" fichier.txt
# Afficher les lignes commencant par un commentaire (#)
grep -E "^\s*#" config.conf
# Trouver les adresses email dans un fichier
grep -oE "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" fichier.txt
11.3 sed - Recherche et remplacement
# Syntaxe de base : sed 's/recherche/remplacement/flags' fichier
# Flag g = global (toutes les occurrences), sans g = premiere seulement
# Remplacement simple
sed 's/ancien/nouveau/g' fichier.txt
# Avec regex etendue (recommande)
sed -E 's/motif/remplacement/g' fichier.txt
# Modifier le fichier en place (attention, pas de retour arriere !)
sed -i 's/ancien/nouveau/g' fichier.txt
# Modifier en place avec backup
sed -i.bak 's/ancien/nouveau/g' fichier.txt
# Exemples concrets
# Reformater des dates JJ/MM/AAAA en AAAA-MM-JJ
sed -E 's/([0-9]{2})\/([0-9]{2})\/([0-9]{4})/\3-\2-\1/g' dates.txt
# Supprimer les balises HTML
sed -E 's/<[^>]+>//g' page.html
# Supprimer les lignes vides
sed '/^$/d' fichier.txt
# Supprimer les espaces en fin de ligne (trailing whitespace)
sed -E 's/\s+$//' fichier.txt
# Supprimer les commentaires (#) d'un fichier de configuration
sed -E '/^\s*#/d' config.conf
# Ajouter un prefixe a chaque ligne
sed 's/^/PREFIX: /' fichier.txt
# Remplacer uniquement sur les lignes contenant un motif
sed '/ERROR/s/warning/CRITICAL/g' log.txt
# Supprimer les lignes 5 a 10
sed '5,10d' fichier.txt
# Extraire entre deux patterns (inclusif)
sed -n '/DEBUT/,/FIN/p' fichier.txt
# Inserer une ligne apres un motif
sed '/pattern/a\Nouvelle ligne inseree' fichier.txt
# Remplacer uniquement la 3eme occurrence par ligne
sed 's/motif/remplacement/3' fichier.txt
# Changement de delimiteur (utile avec des chemins)
sed 's|/var/www/ancien|/var/www/nouveau|g' config.conf
11.4 awk - Traitement de texte avance
# Syntaxe : awk '/pattern/ {action}' fichier
# Afficher les lignes correspondant a un motif
awk '/ERROR/' log.txt
# Afficher une colonne specifique (espace comme separateur par defaut)
awk '{print $1}' fichier.txt # 1ere colonne
awk '{print $NF}' fichier.txt # Derniere colonne
# Separateur personnalise (CSV, etc.)
awk -F',' '{print $2}' fichier.csv # 2eme colonne d'un CSV
awk -F':' '{print $1, $3}' /etc/passwd # Login et UID
# Exemples avec regex
# Lignes ou le 3eme champ correspond a un motif
awk '$3 ~ /^[0-9]+$/ {print $0}' fichier.txt
# Lignes ou le 1er champ NE correspond PAS
awk '$1 !~ /^#/' config.conf
# Extraire les adresses IP uniques d'un log Apache
awk '{print $1}' access.log | sort -u
# Compter les occurrences de chaque code HTTP
awk '{print $9}' access.log | sort | uniq -c | sort -rn
# Calculer la somme d'une colonne pour les lignes correspondantes
awk -F',' '/vente/ {somme += $3} END {print "Total:", somme}' rapport.csv
# Afficher les lignes entre deux motifs
awk '/DEBUT/,/FIN/' fichier.txt
# Formater la sortie
awk -F':' '{printf "%-20s UID: %s\n", $1, $3}' /etc/passwd
# Remplacer un champ selon une condition
awk -F',' 'BEGIN{OFS=","} $3 > 100 {$3 = "ELEVE"} {print}' donnees.csv
11.5 Combinaisons utiles
# Trouver et remplacer recursivement dans tous les fichiers PHP
find /var/www -name "*.php" -exec sed -i 's/ancien/nouveau/g' {} +
# Extraire les erreurs uniques d'un log
grep -oE "Error: .*$" application.log | sort -u
# Compter les occurrences d'un motif par fichier
grep -rc "TODO" /var/www/projet/ --include="*.php" | grep -v ":0$"
# Extraire les URL d'un fichier HTML
grep -oE 'https?://[^"'"'"'> ]+' page.html | sort -u
# Surveiller un log en temps reel avec filtrage
tail -f /var/log/syslog | grep --line-buffered -E "(error|warning|critical)"
# Renommer en masse des fichiers (avec rename)
# Remplacer les espaces par des underscores
rename 's/ /_/g' *.txt
# Trouver les fichiers modifies contenant un pattern
find . -name "*.php" -newer reference.txt -exec grep -l "pattern" {} +
grep -E (ERE) ou grep -P (PCRE) plutot que le grep de base (BRE). La syntaxe ERE est plus proche de ce qu'on utilise en JavaScript/PHP, et PCRE offre les fonctionnalites les plus avancees (lookahead, lookbehind, \d, \w, etc.).
12. Erreurs courantes et performance
12.1 Erreurs frequentes
| Erreur | Probleme | Solution |
|---|---|---|
| Oublier d'echapper le point | 192.168.1.1 correspond aussi a 192x168y1z1 |
Utiliser 192\.168\.1\.1 |
| Quantificateur gourmand par defaut | <.*> capture tout de la premiere < a la derniere > |
Utiliser <.*?> (lazy) ou <[^>]+> |
| Ne pas ancrer le pattern | \d{5} matche "123456" (prend les 5 premiers) |
Utiliser ^\d{5}$ pour une correspondance exacte |
Confondre [ ] et ( ) |
[abc] = un caractere parmi a,b,c ; (abc) = la sequence "abc" |
Utiliser [] pour les classes, () pour les groupes |
Oublier le flag g |
Seule la premiere correspondance est trouvee/remplacee | Ajouter le flag g pour traiter toutes les occurrences |
Le tiret mal place dans [] |
[a-z-0] peut etre mal interprete |
Mettre le tiret en debut ou fin : [-a-z0] ou [a-z0-] |
Doubler les \ en string |
"\d+" ne fonctionne pas en JS (constructeur) ou PHP |
Utiliser "\\d+" dans les strings, ou /\d+/ en litteral |
| Utiliser regex pour du HTML complexe | Les regex ne gerent pas les structures imbriquees | Utiliser un parseur DOM (DOMDocument en PHP, DOMParser en JS) |
12.2 Backtracking (retour en arriere)
Le backtracking est le mecanisme par lequel le moteur regex revient en arriere pour essayer d'autres chemins lorsqu'une tentative de correspondance echoue. C'est normal, mais un exces de backtracking peut rendre une regex extremement lente.
# Comment fonctionne le backtracking ?
# Regex : /a.*b/ sur le texte "a]]]]]]]]]b"
#
# 1. "a" correspond au premier "a"
# 2. ".*" est gourmand : il consomme TOUT le reste "]]]]]]]]b"
# 3. Le moteur essaie de faire correspondre "b" : il n'y a plus rien -> echec
# 4. Le moteur recule d'un caractere (backtrack) : ".*" = "]]]]]]]]"
# 5. Il essaie "b" sur "b" : correspondance trouvee !
#
# Ici le backtracking est minime (1 retour). Mais avec certains patterns...
12.3 Catastrophic Backtracking
Certaines combinaisons de patterns et de textes provoquent un backtracking exponentiel, rendant la regex pratiquement infinie. C'est ce qu'on appelle le catastrophic backtracking (ou ReDoS - Regular Expression Denial of Service).
# DANGER : patterns a eviter (catastrophic backtracking)
# Pattern 1 : quantificateurs imbriques
(a+)+b
# Sur le texte "aaaaaaaaaaaaaaaaaac" (pas de b a la fin)
# Le moteur essaie TOUTES les manieres de repartir les "a" entre les groupes
# Temps exponentiel : 2^n combinaisons !
# Pattern 2 : alternance avec chevauchement
(a|aa)+b
# Meme probleme : multiples facons de decomposer "aaaa..."
# Pattern 3 : repetitions chevauchantes
(\w+\s*)*$
# Sur "mot1 mot2 mot3 mot4 mot5 !" (finit par un caractere non-\w)
# Backtracking exponentiel
# Pattern 4 : nested quantifiers sur classe large
(.*a){10}
# Sur un texte sans assez de "a" : explosion combinatoire
12.4 Bonnes pratiques de performance
| Pratique | Mauvais | Bon | Pourquoi |
|---|---|---|---|
| Eviter les quantificateurs imbriques | (a+)+ |
a+ |
Supprime le backtracking exponentiel |
| Utiliser des classes negees | <.*?> |
<[^>]+> |
Evite le backtracking du lazy quantifier |
| Etre specifique | .+@.+\..+ |
[\w.+-]+@[\w-]+\.\w+ |
Reduit les correspondances inutiles |
| Ancrer les patterns | \d{5} |
^\d{5}$ |
Empeche les tentatives a chaque position |
| Groupes non-capturants | (abc|def)+ |
(?:abc|def)+ |
Evite le cout de sauvegarde des captures |
| Quantificateurs possessifs | \w+\d |
\w++\d (PCRE) |
Pas de backtracking (le moteur ne recule jamais) |
| Atomic groups | (?>abc|ab)c |
(?>abc|ab)c (PCRE) |
Empeche le backtracking dans le groupe |
12.5 Tester la performance
// JavaScript : mesurer le temps d'execution
const debut = performance.now();
const regex = /votre_pattern/g;
const resultat = texte.match(regex);
const duree = performance.now() - debut;
console.log(`Temps: ${duree.toFixed(2)} ms, Resultats: ${resultat?.length ?? 0}`);
// PHP : mesurer le temps d'execution
$debut = microtime(true);
preg_match_all('/votre_pattern/', $texte, $matches);
$duree = (microtime(true) - $debut) * 1000;
echo "Temps: " . number_format($duree, 2) . " ms, Resultats: " . count($matches[0]);
// PHP : limiter le backtracking (php.ini ou ini_set)
ini_set('pcre.backtrack_limit', 1000000); // defaut
ini_set('pcre.recursion_limit', 100000); // defaut
// Bash : mesurer avec time
time grep -cP 'pattern' gros_fichier.log
- Ne jamais executer directement une regex fournie par l'utilisateur
- Limiter la longueur du pattern et du texte a analyser
- Utiliser un timeout (pcre.backtrack_limit en PHP)
- Valider le pattern avec un analyseur statique avant execution
- Envisager des alternatives (recherche par sous-chaines, index full-text)