Les assertions lookahead et lookbehind sont des assertions de largeur nulle : elles vérifient si un motif apparaît ou non à côté de votre correspondance, sans consommer de caractères ni les inclure dans le résultat. Lookahead (?=...) vérifie ce qui suit, lookbehind (?<=...) vérifie ce qui précède, et les deux ont des formes négatives pour "non suivi par" et "non précédé par."
Le terme "largeur nulle" est ce qui pose problème à tout le monde. Un jeton regex normal correspond à un caractère et avance le curseur. Une assertion correspond à une position. Elle examine le texte environnant, renvoie vrai ou faux, et laisse le curseur exactement là où il était. C'est pourquoi vous pouvez empiler plusieurs lookaheads au même endroit, ce qui est l'astuce derrière presque tous les modèles de validation de mot de passe que vous avez déjà copiés.
Les quatre assertions d'anticipation#
Il existe exactement quatre assertions d'anticipation, qui se répartissent clairement selon deux axes : la direction (avant ou arrière) et la polarité (positive ou négative). Une fois que vous les voyez dans un tableau, la syntaxe cesse de paraître arbitraire.
| Assertion | Syntaxe | Signification | En français courant |
|---|---|---|---|
| Anticipation positive | (?=...) | La correspondance doit être suivie de ... | "est suivi de" |
| Anticipation négative | (?!...) | La correspondance ne doit pas être suivie de ... | "n'est pas suivi de" |
| Rétrospection positive | (?<=...) | La correspondance doit être précédée de ... | "vient après" |
| Rétrospection négative | (?<!...) | La correspondance ne doit pas être précédée de ... | "ne vient pas après" |
Les points à l'intérieur de chaque assertion représentent un sous-motif normal. Ils sont évalués, le moteur note s'ils correspondent, puis il annule le déplacement du curseur. Rien de ce qui se trouve dans une assertion d'anticipation ne se retrouve dans votre correspondance ou vos groupes de capture.
Astuce : si vous voulez confirmer qu'une assertion est vraiment de largeur nulle, regardez la longueur du texte correspondant. Un motif composé uniquement d'assertions correspond à une chaîne vide à une position. Le résultat intéressant est où il a correspondu, pas quoi.
Anticipation positive (lookahead) : trouver quelque chose suivi d'autre chose#
Supposons que vous ayez des prix comme 42USD, 99EUR et 7GBP, et que vous souhaitiez uniquement le nombre immédiatement suivi de USD, sans récupérer le code de la devise lui-même.
\d+(?=USD)
Avec 42USD 99EUR 7USD, cela correspond à 42 et 7. Le USD est nécessaire pour que la correspondance ait lieu, mais il n'est jamais capturé, vous obtenez donc des nombres propres que vous pouvez analyser directement. Comparez cela à \d+USD, qui vous oblige à supprimer le USD par la suite.
C'est toute la valeur de l'anticipation : une condition qui conditionne la correspondance sans en faire partie.
Anticipation négative : correspondre à ce qui n'est pas suivi de#
Inversez l'assertion avec (?!...) et vous obtenez "faire correspondre un élément qui n'est pas suivi d'un autre élément." Un cas classique est la correspondance d'un mot qui n'est pas suivi d'un suffixe spécifique.
\bchat(?!alogue)\b
Contre chat catalogue catégorie chats, cela correspond au chat isolé et au chat dans chats (car chats n'est pas catalogue), mais ignore catalogue. Les limites de mot \b empêchent la correspondance du chat enfoui dans un texte non pertinent.
L'anticipation négative permet également les motifs "correspondre à une ligne qui ne contient pas X" lorsqu'on l'ancre :
^(?!.*ERREUR).*$
Cela correspond à toute ligne complète qui ne contient pas le mot ERREUR où que ce soit. L'anticipation scanne d'abord toute la ligne ; si elle trouve ERREUR, l'assertion échoue et la ligne est ignorée. C'est très utile pour filtrer des journaux.
Lookbehind : Vérifier ce qui précède#
Lookbehind fait le même travail dans l'autre sens. Le lookbehind positif (?<=...) exige un motif précédent ; le lookbehind négatif (?<!...) l'interdit.
Un cas courant est l'extraction d'un montant qui suit un symbole monétaire, sans inclure le symbole :
(?<=\$)\d+(\.\d{2})?
Contre Prix : 42,50 € et gratuit, cela correspond à 42,50 et laisse le € en dehors du résultat. Le lookbehind négatif est tout aussi pratique. Pour trouver un nombre qui n'est pas précédé d'un symbole euro :
(?<!\$)\b\d+\b
Cela correspond aux nombres isolés comme 2024 tout en ignorant les valeurs monétaires 99 €. Le lookbehind est là où les différences entre les moteurs commencent à poser problème, ce qui est la prochaine chose à savoir avant de déployer un motif.
Le piège de la portabilité : le lookbehind n'est pas universel#
C'est la partie que la plupart des tutoriels omettent, et c'est celle qui casse le code en production. Le lookahead est supporté presque partout. Le lookbehind ne l'est pas, et même là où il existe, les règles concernant sa longueur variable diffèrent selon les moteurs.
| Moteur / saveur | Lookahead | Lookbehind | Lookbehind de longueur variable |
|---|---|---|---|
| JavaScript (ES2018+) | Oui | Oui | Oui |
| PCRE / PCRE2 (PHP, nombreux outils) | Oui | Oui | Non (longueur fixe uniquement) |
Python (re) | Oui | Oui | Non (longueur fixe uniquement) |
Python (module regex) | Oui | Oui | Oui |
Go (regexp, RE2) | Non | Non | Non |
Deux choses ressortent. Premièrement, le package standard regexp de Go utilise le moteur RE2, qui n'a délibérément aucun lookaround, car RE2 garantit une correspondance en temps linéaire et les lookarounds brisent cette garantie. Si vous écrivez (?<=\$) en Go, cela ne compilera pas. Vous devez restructurer avec des groupes de capture.
Deuxièmement, "lookbehind de longueur fixe" signifie que PCRE et le re intégré de Python rejettent un lookbehind dont la largeur peut varier. (?<=cat|dog) fonctionne car les deux alternatives font 3 caractères ; (?<=cats?) échoue dans ces moteurs car la correspondance pourrait être de 3 ou 4 caractères. JavaScript et le module tiers regex de Python autorisent tous deux le lookbehind de longueur variable, donc le même motif qui fonctionne dans votre console de navigateur peut planter dans un backend Python.
Attention : "fonctionne sur regex101" n'est pas la même chose que "fonctionne dans mon langage." regex101 vous permet de choisir une saveur, mais il est facile de tester sous PCRE puis de déployer sur un service Go ou Python qui rejette le motif. Vérifiez toujours par rapport à votre moteur cible réel.
Un cas d'utilisation concret : validation de politique de mot de passe#
L'endroit le plus courant où les développeurs rencontrent l'anticipation (lookahead) est les règles de mot de passe, et c'est l'exemple le plus clair d'empilement d'assertions de largeur nulle. Supposons que votre politique soit : au moins 8 caractères, avec au moins une lettre minuscule, une lettre majuscule et un chiffre.
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$
Parcourons-la de gauche à droite :
^ancre au début de la chaîne.(?=.*[a-z])vérifie qu'il y a une lettre minuscule quelque part devant. Le curseur ne bouge pas.(?=.*[A-Z])vérifie qu'il y a une lettre majuscule, toujours depuis le début.(?=.*\d)vérifie qu'il y a un chiffre..{8,}$consomme enfin 8 caractères ou plus jusqu'à la fin.
Chaque anticipation scanne indépendamment toute la chaîne depuis la même position de départ, car aucune n'a déplacé le curseur. C'est exactement pourquoi vous pouvez exprimer « doit contenir tout cela, dans n'importe quel ordre » comme une liste plate d'assertions. Sans anticipation, vous auriez besoin d'une alternance complexe couvrant tous les ordres possibles.
Pour interdire les espaces, ajoutez une anticipation négative : (?!.*\s). Pour exiger un caractère spécial, ajoutez (?=.*[!@#$%^&*]). Le motif s'agrandit d'une assertion par règle, ce qui est bien plus lisible que l'alternative.
Étape 1 : Construire les assertions une par une#
Commencez par les ancres et la longueur, puis ajoutez une seule anticipation, et vérifiez son comportement avant d'en ajouter une autre. Collez le motif partiel dans un testeur d'expression régulière gratuit et testez une chaîne qui devrait réussir et une qui devrait échouer. Si vous ajoutez les quatre assertions à la fois et que cela ne fonctionne pas, vous ne saurez pas laquelle est erronée.
Étape 2 : Tester les cas limites, pas seulement le chemin heureux#
Une regex de mot de passe qui accepte Abcdef12 n'est pas prouvée correcte. Testez les chaînes qui devraient échouer : une chaîne entièrement en minuscules, une sans chiffre, une d'exactement 7 caractères. Confirmez que chacune est rejetée pour la bonne raison. Regarder la mise en évidence en direct dans un testeur vous indique si l'échec vient de la longueur ou d'une assertion manquante.
Étape 3 : Verrouiller le moteur avant de copier le motif#
Une fois que ça fonctionne, réglez le testeur sur le moteur que vous utiliserez réellement (JavaScript, PCRE, Python ou Go) et relancez les mêmes cas. Si vous avez utilisé un lookbehind de longueur variable ou tout lookaround dans Go, c'est ici que vous le détecterez, dans l'éditeur, et non dans un build CI qui échoue.
Lookahead vs groupes de capture : quand utiliser quoi#
Une source fréquente de confusion est de savoir quand utiliser un lookahead plutôt qu'un simple groupe de capture, car les deux permettent de « trouver X près de Y ». La différence réside dans ce qui se retrouve dans votre correspondance.
- Utilisez un groupe de capture
(...)lorsque vous voulez conserver le texte environnant et en extraire une partie. La correspondance entière inclut tout ; le groupe isole une partie. - Utilisez une lookaround lorsque le texte environnant n'est qu'une condition et que vous ne voulez pas qu'il apparaisse dans le résultat. La correspondance est uniquement la cible.
Concrètement, (\d+)USD correspond à 42USD et capture 42 dans le groupe 1. \d+(?=USD) correspond uniquement à 42 et ne capture rien. Les deux vous donnent 42, mais le premier conserve USD dans la correspondance globale tandis que le second non. Si vous effectuez une recherche et remplacement et souhaitez laisser USD intact, le lookahead est plus propre car il n'y a rien à remettre.
Pour un travail plus approfondi sur les motifs, notre guide sur la maîtrise des expressions régulières avec un testeur de regex en direct couvre les groupes, les ancres et les drapeaux en plus des lookarounds.
Aide-mémoire rapide#
Gardez-le près de votre éditeur :
(?=foo)correspond à une position suivie defoo(?!foo)correspond à une position non suivie defoo(?<=foo)correspond à une position précédée defoo(?<!foo)correspond à une position non précédée defoo- Les lookaheads sont de largeur nulle : ils affirment, jamais ne consomment.
- Empilez les lookaheads avec
^pour exiger plusieurs conditions dans n'importe quel ordre. - Le lookbehind n'est pas pris en charge dans Go (RE2) et doit être de longueur fixe dans PCRE et le module
reintégré de Python.
Lorsque vous devez valider ou démêler un motif, collez-le dans le testeur d'expressions régulières Molixa et regardez les correspondances et les groupes se mettre à jour en direct, puis changez de moteur pour confirmer la compatibilité. Si vous manipulez aussi des charges utiles d'API, le formateur JSON est idéal pour nettoyer les réponses avant d'y appliquer des regex, et si vous avez utilisé regex101, notre alternative gratuite à regex101 explique les différences.
Foire aux questions#
Quelle est la différence entre l'anticipation et la rétrospection en regex ?
L'anticipation (?=...) vérifie le texte qui vient après la position actuelle, tandis que la rétrospection (?<=...) vérifie le texte qui vient avant. Les deux sont des assertions de largeur nulle, ce qui signifie qu'elles testent une condition sans inclure le texte environnant dans votre correspondance. Chacune a également une forme négative, (?!...) et (?<!...), pour "non suivi de" et "non précédé de."
Est-ce que JavaScript prend en charge la rétrospection en regex ?
Oui. JavaScript prend en charge la rétrospection et la rétrospection de longueur variable depuis ES2018, donc (?<=\$)\d+ fonctionne dans les navigateurs modernes et Node. Les environnements plus anciens antérieurs à ES2018 ne le font pas. Si vous ciblez un environnement hérité, testez-y ou restructurez avec un groupe de capture au lieu d'une rétrospection.
Pourquoi ma rétrospection fonctionne-t-elle en JavaScript mais échoue-t-elle en Python ?
Il s'agit très probablement d'une rétrospection de longueur variable. Le module re intégré de Python n'autorise que la rétrospection de longueur fixe, donc (?<=cats?) échoue car la largeur varie. JavaScript autorise la rétrospection de longueur variable, donc le même motif y passe. Utilisez le module tiers regex en Python pour la prise en charge de la longueur variable, ou réécrivez l'assertion en une largeur fixe.
Comment utiliser l'anticipation pour la validation de mot de passe ?
Empilez une anticipation positive par règle au début du motif, puis consommez les caractères. Par exemple, ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$ exige une lettre minuscule, une lettre majuscule, un chiffre et une longueur minimale de 8. Comme les anticipations sont de largeur nulle, chacune scanne indépendamment toute la chaîne depuis la même position.
Est-ce que Go prend en charge l'anticipation et la rétrospection en regex ?
Non. Le package regexp standard de Go utilise le moteur RE2, qui ne prend pas du tout en charge les assertions de contexte, par conception, pour garantir un temps de correspondance linéaire. Les motifs avec (?=...) ou (?<=...) ne se compileront pas en Go. Vous restructurez la logique avec des groupes de capture, ou utilisez une liaison PCRE tierce si vous avez vraiment besoin d'assertions.
Quand utiliser une anticipation plutôt qu'un groupe de capture ?
Utilisez une anticipation lorsque le texte voisin n'est qu'une condition et que vous ne voulez pas l'inclure dans votre résultat. Utilisez un groupe de capture lorsque vous voulez le texte environnant dans la correspondance et devez isoler une partie de celui-ci. Par exemple, \d+(?=USD) renvoie uniquement le nombre, tandis que (\d+)USD renvoie le nombre mais conserve USD dans la correspondance globale.


