Chapitre 8.9Les expressions régulières

Des motifs pour chercher et valider du texte.

6 minutes de lecture

Imagine que tu cherches dans un document tous les numéros de téléphone français. Ils peuvent être écrits de dizaines de façons, 06 12 34 56 78, 0612345678, +33 6 12 34 56 78, 06.12.34.56.78, et la liste continue.

Tu pourrais écrire une règle de recherche pour chaque format, et en oublier la moitié. Ou tu pourrais utiliser une expression régulière, souvent abrégée en "regex", un mini-langage qui décrit des motifs de texte.

Ce chapitre ne va pas faire de toi un expert. Il va te permettre de lire une regex qu'un développeur te met sous les yeux, de comprendre pourquoi la validation d'un formulaire refuse une adresse pourtant valide, et de savoir quand ce n'est pas le bon outil.

Le principe

Une expression régulière est une formule qui décrit un schéma de texte. Au lieu de chercher un texte exact, on décrit ce à quoi le texte ressemble.

L'idée n'est pas récente. Elle vient des mathématiques des années 1950 et a été intégrée aux outils Unix dans les années 1970, avec des commandes comme grep. Depuis, à peu près tous les langages de programmation et tous les éditeurs de texte savent en manipuler.

Commençons par les briques de base.

  • [0-9] : un chiffre quelconque, de 0 à 9. Les crochets décrivent un ensemble de caractères possibles, et on n'en prend qu'un seul.
  • [a-z] : une lettre minuscule quelconque. On peut cumuler, [a-zA-Z0-9] accepte n'importe quelle lettre ou n'importe quel chiffre.
  • [^0-9] : le chapeau en début de crochets inverse la règle, ici n'importe quel caractère sauf un chiffre.
  • . (un point) : n'importe quel caractère. Si tu veux un vrai point, il faut l'échapper en écrivant \..
  • | : une alternative, qui porte cette fois sur tout ce qui l'entoure. chat|chien trouve l'un ou l'autre.

À part l'alternative, ces briques décrivent un seul caractère à la fois. Pour en décrire plusieurs, on ajoute un quantificateur juste derrière.

  • + : un ou plusieurs de ce qui précède.
  • * : zéro ou plusieurs de ce qui précède.
  • ? : zéro ou un, autrement dit optionnel.
  • {3} : exactement trois fois.
  • {2,} : au moins deux fois.
  • {2,4} : entre deux et quatre fois.

Quelques raccourcis très courants reviennent tout le temps et méritent d'être reconnus au premier coup d'oeil.

  • \d : un chiffre, l'équivalent exact de [0-9].
  • \w : un caractère de mot, c'est-à-dire une lettre non accentuée, un chiffre ou un tiret bas.
  • \s : un espace, une tabulation ou un retour à la ligne.
  • \b : une frontière de mot. C'est ce qui distingue une recherche de "chat" qui trouve aussi "chaton" et "achat" d'une recherche \bchat\b qui ne trouve que le mot entier.

Enfin, deux symboles servent à ancrer le motif. Le chapeau ^ en début d'expression signifie "à partir du début du texte", et le dollar $ à la fin signifie "jusqu'à la fin du texte". Sans eux, on cherche le motif n'importe où dans le texte.

C'est une source de bugs classique en validation de formulaire. Si tu vérifies un code postal avec \d{5} sans ancres, la saisie "mon code est 75001 merci" passe le test, puisque cinq chiffres consécutifs y figurent bien. Avec ^\d{5}$, seule une saisie composée exactement de cinq chiffres est acceptée.

Un exemple déroulé

Reprenons la promesse du début. Voici une expression qui reconnaît les principaux formats de numéro français.

Numéro de téléphone français
(?:0|\+33 ?)[1-9](?:[ .-]?[0-9]{2}){4}

Elle a l'air effrayante. Elle ne l'est pas, il suffit de la lire de gauche à droite, morceau par morceau.

  • (?:0|\+33 ?) : soit un zéro, soit l'indicatif +33 éventuellement suivi d'un espace. Les parenthèses servent à regrouper une portion de motif, et le ?: juste après la parenthèse ouvrante indique simplement qu'on ne veut pas mémoriser ce morceau pour le réutiliser ensuite.
  • [1-9] : le premier chiffre du numéro, qui n'est jamais un zéro.
  • [ .-]? : un séparateur optionnel, au choix un espace, un point ou un tiret. Dans des crochets, le point ne veut plus dire "n'importe quel caractère", il redevient un simple point.
  • [0-9]{2} : deux chiffres.
  • {4} à la fin : le groupe séparateur plus deux chiffres se répète quatre fois, ce qui donne bien huit chiffres après le premier.

Déroulons sur "06 12 34 56 78". Le zéro initial est reconnu, puis le 6, puis quatre fois de suite un espace suivi de deux chiffres. Sur "0612345678", même chose sans les espaces, puisque le séparateur est optionnel. Sur "+33 6 12 34 56 78", c'est la branche \+33 ? qui s'applique et la suite est identique.

Note qu'elle accepte aussi des écritures incohérentes comme "06 12.34-56 78", qui mélange les séparateurs. C'est très souvent le cas, une regex reconnaît un peu plus large que ce qu'on avait en tête. Tant qu'elle ne rejette pas de numéros valides, c'est généralement un compromis acceptable.

Où les utilise-t-on ?

Les regex sont partout en informatique, et pas uniquement dans le code.

  • La validation de formulaires : vérifier qu'un email, un numéro de téléphone ou un code postal est au bon format avant de l'envoyer au serveur.
  • La recherche et le remplacement : trouver et modifier des motifs dans du texte. "Remplace tous les numéros de carte bancaire par des étoiles." La fonction Rechercher/Remplacer de ton éditeur de texte propose presque toujours une option regex.
  • L'extraction de données : récupérer des informations structurées depuis du texte brut, par exemple toutes les dates d'un document avec \d{2}/\d{2}/\d{4}.
  • Le filtrage de logs : retrouver les lignes contenant une erreur précise parmi des millions d'autres. Les outils de monitoring reposent massivement sur ce mécanisme.
  • La configuration serveur : les règles de redirection d'URL d'un site web s'écrivent avec des regex. Quand tu demandes "toutes les vieilles URLs /produit/... doivent rediriger vers /p/...", c'est une regex qui fait le travail. Et c'est là que le remplacement entre en jeu, on capture un morceau du motif entre parenthèses, l'identifiant du produit, pour le réinjecter dans la nouvelle adresse. Reconnaître n'est que la moitié de l'usage, réécrire est l'autre.
  • Tes propres outils : tu en écris probablement déjà sans le savoir. Les filtres avancés d'un outil de mesure d'audience, les segments d'un CRM, les règles de tri d'une messagerie et la recherche de certains tableurs acceptent des regex. "Toutes les pages dont l'adresse commence par /blog/ et se termine par un chiffre", c'est une regex de dix caractères.

Un détail explique à lui seul la moitié des "ma recherche ne trouve rien". Par défaut, majuscules et minuscules ne sont pas équivalentes, chat ne trouve pas "Chat". La plupart des outils proposent une option pour ignorer la casse, souvent une petite case ou un drapeau i à côté du champ.

Exemple :

L'équipe support exporte 40 000 messages clients et veut isoler ceux qui contiennent un numéro de commande. Les numéros ont tous la forme CMD suivie de six chiffres. Une recherche avec CMD\d{6} règle la question en quelques secondes, là où un filtre par mot-clé aurait ramené tous les messages contenant le mot "commande".

La réputation

Les expressions régulières ont une réputation bien méritée. Elles sont incroyablement puissantes et souvent illisibles, y compris pour la personne qui les a écrites trois mois plus tôt.

Une blague popularisée par le développeur Jamie Zawinski en 1997 résume la chose.

Certains, face à un problème, se disent "je sais, je vais utiliser une expression régulière". Ils ont maintenant deux problèmes.

L'exemple canonique est la validation d'adresse email. Le motif suivant est celui qu'on utilise en pratique.

Validation d'email, version raisonnable
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$

Il se lit ainsi. Un ou plusieurs caractères parmi les lettres, les chiffres, le point, le tiret bas, le pourcentage, le plus et le tiret. Puis une arobase. Puis un ou plusieurs caractères parmi les lettres, les chiffres, le point et le tiret. Puis un point, cette fois échappé donc bien littéral. Puis au moins deux lettres pour l'extension.

Il accepte marie.dupont@gmail.com, contact@entreprise.fr ou encore jean+newsletter@mail.co.uk. Il refuse en revanche les adresses avec accents et quelques constructions exotiques pourtant légales.

Une regex qui validerait les adresses selon la spécification officielle complète fait plus de 6 000 caractères. Personne ne la relit, personne ne la maintient, et elle laisse quand même passer des adresses qui n'existent pas. C'est pourquoi la bonne pratique consiste à faire une validation grossière du format, puis à envoyer un email de confirmation. Seule la réception prouve que l'adresse est réelle.

Les pièges à connaître

Trois travers reviennent assez souvent pour valoir la peine d'être signalés.

La gourmandise

Par défaut, les quantificateurs sont "gourmands", ils prennent le plus de texte possible. Appliqué au texte <b>gras</b>, le motif <.+> ne capture pas la balise ouvrante seule mais la chaîne entière, du premier chevron au dernier. Ajouter un point d'interrogation après le quantificateur, <.+?>, le rend "paresseux" et lui fait s'arrêter au plus tôt, sur <b>.

C'est l'explication de la moitié des "ma regex prend trop de texte".

Les accents et l'alphabet

[a-z] et \w ne couvrent que l'alphabet latin non accentué. Un motif écrit avec ces raccourcis rejettera "Benoît", "Nuñez" ou n'importe quel nom en cyrillique ou en arabe. Sur un service qui vise l'international, c'est une source de discrimination silencieuse, et sur un formulaire d'inscription, ce sont des clients refusés à la porte pour le seul crime de porter leur nom. Les regex modernes savent raisonner sur les catégories Unicode, encore faut-il y penser.

La performance

Certaines expressions mal construites, typiquement celles qui empilent des quantificateurs imbriqués comme (a+)+$, peuvent exploser en temps de calcul sur une chaîne un peu longue. Le moteur essaie un nombre astronomique de combinaisons avant d'abandonner.

C'est une véritable famille de failles de sécurité, connue sous le nom de ReDoS. Un attaquant envoie une chaîne soigneusement choisie dans un champ de formulaire et bloque le serveur avec une seule requête. Des pannes majeures de services web ont été causées par une regex de ce type déployée en production.

En pratique

Personne n'apprend les regex par coeur. Les développeurs en utilisent une poignée de simples au quotidien et passent par des outils en ligne comme regex101.com pour construire et tester les plus complexes. Ces outils décomposent l'expression morceau par morceau et montrent en direct ce qui est reconnu, ce qui rend l'apprentissage bien plus facile qu'à la lecture.

Aujourd'hui, la façon la plus courante d'obtenir une regex complexe est de la demander à un assistant d'IA, et ça marche très bien. À une condition, la tester sur de vrais exemples, y compris et surtout sur ceux qui doivent être refusés. Une regex qui accepte tout ce qu'on lui montre a l'air de fonctionner, c'est celle qui laisse passer "mon code est 75001 merci".

De mon expérience, le vrai réflexe à acquérir est ailleurs. Avant d'écrire une regex, demande-toi si un outil dédié n'existe pas. Pour découper du texte sur une virgule, une simple fonction de découpage suffit. Pour analyser du JSON ou du HTML, il faut un analyseur syntaxique, jamais une regex. Vouloir extraire des données d'une page HTML avec une expression régulière est un classique du genre, et ça finit toujours mal.

Et si tu es côté produit, retiens surtout ceci. Quand un utilisateur se plaint que le formulaire refuse son adresse ou son numéro pourtant valides, la cause est presque toujours une regex de validation trop stricte. Une règle trop serrée fait perdre des inscriptions, ce qui coûte bien plus cher que quelques saisies un peu farfelues à nettoyer plus tard.

PrécédentFrameworks et bibliothèques Tous les chapitres