Une regex, un « langage de motifs »
Une expression régulière décrit la forme d'une famille de chaînes avec un petit jeu de symboles. Les lettres et les chiffres se représentent le plus souvent eux-mêmes ; une quinzaine de métacaractères (. * + ? ^ $ | ( ) [ ] { } \) ont un sens particulier. Le moteur parcourt le texte de gauche à droite, tente une correspondance à chaque position et, en cas de succès, en rapporte la position et la longueur. Ce cours utilise le dialecte JavaScript (ECMAScript), presque identique à la syntaxe de base de Python, Java ou Go ; les différences portent sur les lookarounds, les propriétés Unicode et certains drapeaux.
Niveaux 1–3 : littéraux, point et échappement
cat correspond aux caractères c, a, t à la suite. Le point . reconnaît n'importe quel caractère hors retour à la ligne : c.t accepte cat, cut ou c9t. Pour chercher un vrai point (celui qui précède une extension de fichier, par exemple), écrivez \. — l'antislash ramène le métacaractère à sa valeur littérale. Tous les métacaractères s'échappent ainsi ; dans une classe de caractères, seuls ] \ ^ - doivent l'être.
Niveaux 4–7 : classes de caractères
Les crochets listent des candidats : [aeiou] reconnaît n'importe quelle voyelle ; [a-z] et [0-9] expriment des plages avec le tiret, combinables en [A-Za-z0-9_] ; un ^ en tête inverse le sens, [^0-9] accepte tout sauf un chiffre. Trois classes prédéfinies courantes en sont des abréviations :
| Classe |
Équivalent |
Remarque |
\d |
[0-9] |
chiffre (ni chiffres chinois, ni chiffres pleine chasse) |
\w |
[A-Za-z0-9_] |
« caractère de mot » ; les caractères chinois n'en font pas partie |
\s |
[ \t\n\r\f\v…] |
espace blanc, y compris l'espace pleine chasse |
Les versions majuscules \D \W \S sont les négations. Le \w qui ignore les caractères chinois est le piège le plus fréquent : pour reconnaître du chinois, écrivez [\u4e00-\u9fa5], ou \p{Script=Han} sous drapeau u.
Niveaux 8–10 : quantificateurs
Un quantificateur suit un atome (caractère, classe ou groupe) et fixe son nombre de répétitions :
* zéro ou plus, + une ou plus, ? zéro ou une ;
{n} exactement n fois, {n,} au moins n fois, {n,m} de n à m fois.
\d{4} se lit mieux que \d\d\d\d, et \d{11} donne le nombre de chiffres d'un numéro de téléphone. Un quantificateur ne porte que sur l'atome juste avant : ab+ est un a suivi d'un ou plusieurs b ; pour répéter tout ab, il faut (ab)+.
Niveaux 11–12 : ancres et limites
^ et $ ne correspondent à aucun caractère, seulement à une position : début et fin de chaîne (début et fin de chaque ligne en mode multiligne). Une validation de formulaire devrait presque toujours s'écrire ^…$, sinon \d{11} « correspond » aux 11 premiers chiffres d'un numéro qui en compte 12. \b est la limite de mot — la position entre \w et \W (ou une extrémité). \bcat\b reconnaît a cat mais pas concatenate. Et comme \w ignore le chinois, \b y est pratiquement inutile.
Niveaux 13–17 : alternance, groupes et références
| signifie « ou », avec la priorité la plus basse : cat|dog accepte l'un des deux mots ; pour limiter la portée, il faut des parenthèses : gr(a|e)y. Les parenthèses créent en même temps un groupe de capture : après une correspondance, on récupère le texte du groupe par son numéro (match[1]) ou on le réutilise dans la chaîne de remplacement avec $1 — l'un des usages les plus productifs, comme transformer 2026-09-09 en 09/09/2026 d'un seul replace.
(?<year>\d{4}) est un groupe nommé, lu par groups.year, moins fragile qu'un numéro quand le motif évolue. (?:…) est un groupe non capturant : il regroupe sans occuper de numéro, pratique pour délimiter la portée d'un quantificateur ou d'une alternance. \1 est une rétroréférence — exiger que le contenu capturé par le groupe 1 réapparaisse ici : (\w)\1 reconnaît les doubles lettres comme ll ou oo, et <(\w+)>.*?</\1> des balises HTML par paires.
Niveau 18 : glouton et paresseux
Par défaut, un quantificateur est glouton : il en prend le plus possible, quitte à en rendre. Sur "a" and "b", ".+" avale tout du premier guillemet au dernier. Ajouter ? après le quantificateur le rend paresseux : ".+?" en prend le moins possible et donne deux correspondances, "a" et "b". Une écriture plus précise consiste à utiliser une classe négative "[^"]+", qui ne dépend pas du backtracking et est généralement plus rapide.
Niveaux 19–20 : assertions lookaround
Un lookaround ne correspond, comme une ancre, qu'à une position, mais sa condition est « y a-t-il tel motif à côté » :
(?=…) lookahead : ce qui suit doit être … ; \d+(?=€) correspond au 100 de 100€ sans inclure le symbole.
(?!…) lookahead négatif : ce qui suit ne doit pas être ….
(?<=…) lookbehind : ce qui précède doit être … ; (?<=€)\d+ capture le nombre d'un prix sans le symbole.
(?<!…) lookbehind négatif.
JavaScript accepte le lookbehind depuis ES2018, avec une disponibilité complète seulement depuis Safari 16.4 ; en Python, il doit être de longueur fixe. La validation de mot de passe est l'usage type : ^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$ vérifie depuis la même position, par trois lookaheads, « contient un chiffre, une minuscule, une majuscule », puis .{8,} consomme toute la chaîne.
Niveau 21 : drapeaux
Les drapeaux s'écrivent après le motif : i ignore la casse ; g cherche toutes les correspondances au lieu de la première (le surlignage du cours utilise toujours g) ; m fait correspondre ^ $ à chaque ligne ; s laisse . reconnaître aussi le retour à la ligne ; u active le mode Unicode, . avance alors par point de code et non par unité UTF-16, et un emoji compte pour un caractère ; y est le mode collant, où la correspondance doit commencer exactement à lastIndex.
Niveaux 22–24 : trois motifs courants
Numéro de téléphone de Chine continentale : ^1[3-9]\d{9}$ — commence par 1, deuxième chiffre de 3 à 9, 11 chiffres en tout. N'essayez pas d'énumérer les plages d'opérateurs : elles s'ajoutent chaque année.
E-mail : une expression strictement conforme à la RFC 5322 fait des milliers de caractères et n'est pas praticable. L'écriture raisonnable dans un formulaire est ^[^\s@]+@[^\s@]+\.[^\s@]+$ : un seul @, des deux côtés non vides, et au moins un point dans le domaine. La vraie validation, c'est l'envoi d'un e-mail de confirmation.
Date : ^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ borne le mois à 01–12 et le jour à 01–31, mais ne sait pas combien de jours compte février — la syntaxe au regex, la sémantique au code.
Niveau 25 : backtracking catastrophique
Les moteurs de JavaScript, Python, Java et PCRE fonctionnent par retour arrière (backtracking) : face à plusieurs découpages possibles, ils en essaient un, puis reviennent essayer le suivant en cas d'échec. Des quantificateurs imbriqués font exploser le nombre de découpages : ^(a+)+$ face à 30 a suivis d'un ! doit essayer plus de 2^30 façons de découper avant de déclarer l'échec — l'onglet se fige de quelques secondes à quelques minutes. Si un tel motif reçoit une entrée utilisateur, c'est une attaque par déni de service (ReDoS).
Les principes pour l'éviter : ne pas laisser deux quantificateurs voisins reconnaître les mêmes caractères ((a+)+, (\w+\s?)+, (.*)* sont des signaux d'alerte) ; remplacer le paresseux .*? par une classe négative ; plafonner la longueur des entrées. Les bibliothèques de Go et Rust reposent sur Thompson NFA / DFA et garantissent un temps linéaire, au prix des rétroréférences et des lookarounds — l'article de Russ Cox explique le mieux cette différence d'implémentation.
Ce pour quoi une regex n'est pas faite
Reconnaître des structures imbriquées (appariement de parenthèses, HTML complet), compter (vérifier l'égalité du nombre de parenthèses ouvrantes et fermantes), juger selon le contexte (cette date est-elle valide) : une regex n'y arrive pas, ou très difficilement. C'est un outil de la couche lexicale : retrouver des tokens, découper, valider simplement, remplacer en masse — et là, elle est irremplaçable.