Votre regex ne correspond pas ? La checklist de débogage
Quand une regex qui semble juste ne correspond pas, la cause est généralement l'une d'une douzaine de choses : des caractères invisibles, des barres obliques inverses perdues, un point non échappé, une ancre ou un marqueur manquant. Les voici dans l'ordre où les vérifier, avec des exemples à essayer.

· 7 min de lecture
/1\.5/
à attraper
- 1.5
- v1.5.2
à épargner
- 125
- 1x5
Quand une regex semble juste mais ne correspond pas, le motif est rarement faux de façon intéressante. C'est généralement l'une d'une douzaine de causes ordinaires : l'entrée contient un caractère invisible, une barre oblique inverse s'est perdue dans une chaîne, un caractère spécial n'est pas échappé, une ancre ou un marqueur manque, ou le code exécute la regex autrement que votre testeur. Voici une checklist dans l'ordre où il vaut la peine de vérifier, avec des exemples interactifs.
Les exemples utilisent le moteur de JavaScript ; quand Python ou un autre langage se comporte différemment, le point concerné le précise.
01D'abord, une méthode en 60 secondes
- Affichez l'entrée telle quelle. En Python,
print(repr(text))montre les caractères invisibles sous forme d'échappements comme\xa0(une espace insécable),\u200b(une espace de largeur nulle) ou\r. En JavaScript,[...text].map(c => c.codePointAt(0).toString(16))liste le code de chaque caractère, si bien quea0ou200bsautent aux yeux. - Réduisez le motif. Supprimez des morceaux en partant de la fin jusqu'à ce qu'il corresponde, puis rajoutez-les un par un. Le morceau qui le casse, c'est le bug.
- Écrivez deux listes de lignes de test : quelques-unes qui doivent correspondre et quelques-unes qui ne le doivent pas. Collez les deux dans le Bac à sable, dont le panneau Anatomie explique chaque partie du motif en français clair.
02L'entrée n'est pas ce que vous croyez
Le texte copié depuis des pages web, des PDF et des messageries contient souvent des caractères qui ressemblent aux caractères ordinaires sans l'être : une espace insécable au lieu d'une espace, une espace de largeur nulle au milieu d'un mot, des guillemets courbes au lieu de guillemets droits. Les deux lignes ci-dessous ont l'air identiques. La seconde a une espace insécable après les deux-points, donc un motif avec une espace normale la rate :
En JavaScript, \s reconnaît les deux sortes d'espace ; c'est donc la solution quand la source n'est pas fiable :
Une espace de largeur nulle est pire : hello s'affiche « hello » mais compte six caractères, et ^hello$ ne le reconnaît pas. Les guillemets courbes “hi” ne correspondent pas à "hi". Afficher l'entrée (étape 1 ci-dessus) révèle tout cela.
03Les barres obliques inverses n'ont pas survécu
Un motif écrit dans une chaîne normale perd ses barres obliques inverses avant que le moteur regex ne le voie :
- JavaScript :
new RegExp("\d+")devient le motifd+, qui reconnaîtdddet pas42. Écriveznew RegExp("\\d+"), ou utilisez un littéral :/\d+/. - Python : utilisez toujours des chaînes brutes,
r"\bcat\b". Sans ler,"\b"est un caractère de retour arrière, donc"\bcat\b"ne reconnaît jamaisa catet ne signale aucune erreur. - Java : chaque barre oblique inverse est doublée dans le code source :
"\\d+","\\.".
Si un motif fonctionne dans un testeur mais pas dans le code, c'est le premier suspect.
04Un caractère spécial n'est pas échappé
. * + ? ^ $ | ( ) [ ] { } \ ont tous un sens. Le point non échappé est le cas classique, car le motif « fonctionne » encore, mais trop bien : 1.5 reconnaît 125.
Il en va de même pour $ dans les prix, ? dans les URL et + dans les numéros de téléphone : mettez une barre oblique inverse devant, ou placez le caractère dans une classe comme [.] ou [+].
05Il reconnaît une partie de la chaîne, ou rien du tout
Une recherche regex trouve une correspondance n'importe où dans le texte. \d{3} « valide » 12345abc parce que trois chiffres y apparaissent quelque part. Pour valider, ancrez les deux extrémités : ^\d{3}$.
Python ajoute une subtilité : re.match n'ancre que le début, donc re.match(r"\d{3}", "12345") réussit. Utilisez re.fullmatch pour « toute la chaîne ».
Le problème inverse : ^ et $ désignent le début et la fin de tout le texte, sauf si vous activez le marqueur m, donc ^ERROR ne trouve rien à la ligne 5 d'un log tant que vous n'ajoutez pas m. Et les fichiers avec des fins de ligne Windows (\r\n) piègent Python : avec re.M, son $ ne correspond pas avant \r, donc ^abc$ échoue sur abc\r\n. Le $ de JavaScript, lui, y correspond. Retirez d'abord le \r, ou autorisez-le : ^abc\r?$.
Plus de détails dans Les ancres en regex.
06Il manque un marqueur
i: sans lui,hellone reconnaît pasHello.s: sans lui,.ne reconnaît pas un saut de ligne, donca.brateaetbsur deux lignes.g: sans lui, lereplacede JavaScript ne change que la première correspondance :"a-b-c".replace(/-/, "+")donnea+b-c.u: sans lui, un emoji compte pour deux caractères, donc^.$ne reconnaît pas😀.
07Le marqueur g se souvient de l'endroit où il s'est arrêté (JavaScript)
Une regex avec le marqueur g conserve lastIndex d'un appel à l'autre. Réutilisez-la avec test() et les résultats alternent :
const re = /cat/g;
re.test("cat"); // true
re.test("cat"); // false: the search started at index 3
re.test("cat"); // true again
Retirez le g pour test(), ou créez la regex dans la fonction. C'est pareil pour exec() dans une boucle qui n'atteint jamais la fin.
08Il en a reconnu trop
.* est gourmand : dans <b>x</b><b>y</b>, le motif <b>.*</b> prend tout du premier <b> jusqu'au dernier </b>. Utilisez .*?, ou mieux, une classe qui ne peut pas dépasser la fin, comme [^<]*. Quantificateurs gourmands ou paresseux détaille le sujet.
09La classe de caractères ne dit pas ce que vous croyez
[a-Z]est une erreur (« range out of order ») : les majuscules viennent avant les minuscules. Écrivez[a-zA-Z].- Un tiret au milieu crée une plage.
[\w-.]fonctionne par hasard sans le marqueuruet provoque une erreur avec. Placez le tiret en premier ou en dernier :[\w.-]. ^n'est une négation qu'au tout début.[^abc]signifie « ni a, ni b, ni c » ;[a^bc]signifie « a, ^, b ou c ».
10Votre testeur utilise un autre moteur
Un motif construit dans un testeur PCRE peut échouer en JavaScript, en Python ou en Go : les groupes nommés s'écrivent différemment, les limites du lookbehind varient et le moteur de Go n'a aucun lookaround. Moteurs regex comparés donne le tableau. Testez dans le moteur qu'utilise votre code ; le Bac à sable utilise celui de JavaScript.
11Il n'échoue pas, il se bloque
Si la regex prend plusieurs secondes sur certaines entrées, ce n'est pas un problème de correspondance : des quantificateurs imbriqués comme (a+)+ font essayer à un moteur à backtracking un nombre énorme de chemins. Le backtracking catastrophique montre comment le repérer et le corriger.
12Éviter qu'il revienne
Conservez les lignes de test à côté du motif : un commentaire avec deux ou trois lignes qui doivent correspondre et deux ou trois qui ne le doivent pas, ou un test unitaire. Le Bac à sable permet de partager le motif et vos lignes de test en un seul lien, pratique lors d'une revue de code. Et si vous voulez que ces réflexes de débogage deviennent automatiques, la chasse du jour, c'est exactement cela en cinq minutes : des lignes à attraper, des lignes à épargner et un motif à corriger jusqu'à ce que les deux soient justes.