Vous avez écrit des motifs ; il est temps de les faire tourner. En JavaScript — dans le navigateur comme dans Node.js — les expressions régulières sont des objets de première classe, avec leur propre syntaxe littérale et une poignée de méthodes. Dans ce tutoriel, on transforme les motifs du parcours en code qui marche : on va écrire un petit analyseur qui lit les lignes de log de Téranga Livraison et les transforme en objets exploitables.
📍 Article principal du parcours : Maîtriser les expressions régulières : le guide complet
Ce tutoriel fait partie du parcours « Expressions régulières ». Commencez par le guide pour la vue d’ensemble.
🎯 Ce que vous allez apprendre
- Créer une regex de deux façons — le littéral
/.../et le constructeurnew RegExp()— et savoir laquelle choisir. - Maîtriser les drapeaux (flags)
g i m s u y d vet ce que chacun change. - Utiliser
test(),exec(),match(),matchAll()et lire les groupes nommés via.groups. - Remplacer avec
replace()/replaceAll(), les références$<nom>et une fonction de remplacement. - Sécuriser une saisie utilisateur avec
RegExp.escape()avant de l’injecter dans un motif.
🛠️ Ce que vous allez construire
Une fonction analyser(lignes) qui prend les lignes brutes du journal et renvoie un tableau d’objets {ip, methode, chemin, statut}, prêts à être comptés, filtrés ou affichés. C’est la version exécutable du motif de découpe construit au tutoriel sur les groupes.
Prérequis
- Node.js installé (au moment d’écrire, la version 24 LTS) ou simplement la console du navigateur.
- Avoir suivi Groupes, captures et alternation et les assertions.
- Niveau : intermédiaire. ⏱️ ~40 minutes.
Étape 1 — Deux façons de créer une regex
JavaScript propose deux constructeurs. Le littéral s’écrit entre deux barres obliques, suivies des flags. Le constructeur new RegExp() prend une chaîne et une chaîne de flags. Le littéral est plus lisible et compilé une seule fois ; le constructeur sert quand le motif est dynamique, construit à partir d’une variable.
// Littéral : à privilégier quand le motif est connu à l'écriture
const ref = /CMD-\d{4}-\d{5}/;
// Constructeur : indispensable quand le motif vient d'une variable
const champ = "statut";
const dynamique = new RegExp(champ + "=(\\d+)");
Notez le piège du constructeur : comme on passe une chaîne, chaque barre oblique inverse doit être doublée. \d dans un littéral devient "\\d" dans une chaîne, parce que la chaîne consomme déjà un niveau d’échappement. C’est la cause n°1 des regex « qui marchent en littéral mais pas en new RegExp ». Règle simple : si votre motif est figé, écrivez-le en littéral ; ne sortez le constructeur que pour les motifs assemblés à la volée.
✅ Point d’étape — Dans la console, tapez
/CMD-\d{4}-\d{5}/.test("CMD-2026-00428"). Vous devez obtenirtrue. Essayez ensuitenew RegExp("CMD-\\d{4}").test("CMD-2026"):trueaussi. Oubliez un antislash dans la version chaîne et observez l’échec.
Étape 2 — Les drapeaux (flags)
Les flags modifient le comportement global du moteur. On les place après la barre fermante (/motif/gi) ou en deuxième argument du constructeur. Les connaître évite des heures de confusion.
g global → trouve toutes les occurrences, pas seulement la première
i insensible → ignore la casse (A = a)
m multiligne → ^ et $ s'ancrent à chaque ligne, pas au texte entier
s dotAll → le point . reconnaît aussi le saut de ligne (ES2018)
u unicode → interprète correctement les caractères Unicode et \p{...}
y sticky → ancre la recherche exactement sur lastIndex
d indices → expose les positions de chaque groupe via .indices (ES2022)
v unicodeSets → sur-ensemble de u : opérations d'ensemble dans [...] (ES2024)
Les plus utilisés au quotidien sont g et i. Le flag m est précieux pour traiter un fichier multiligne d’un coup : avec lui, ^\d{1,3} s’accroche au début de chaque ligne. Le flag s (ajouté en ES2018) résout le cas classique où l’on veut que le point traverse les retours à la ligne. Les flags d (ES2022) et v (ES2024) sont plus récents : v, en particulier, débloque des opérations d’ensemble comme [\p{Letter}--[aeiou]] dans les classes. On les mentionne pour que vous les reconnaissiez ; au quotidien, g, i et m couvrent l’essentiel.
Étape 3 — test() et exec(), et le piège de lastIndex
test() renvoie un booléen : le motif correspond-il, oui ou non ? exec() renvoie le détail de la première correspondance (ou null). Mais attention : dès qu’une regex porte le flag g, elle devient stateful — elle mémorise une position lastIndex et reprend la recherche là où elle s’était arrêtée.
const re = /\d+/g;
re.test("428"); // true, lastIndex passe à 3
re.test("428"); // false ! lastIndex était à 3, plus rien à droite
re.test("428"); // true, lastIndex remis à 0 après l'échec
Ce comportement « une fois vrai, une fois faux » sur la même chaîne déroute tous les débutants. La leçon : n’utilisez pas g avec test() si vous appelez la même regex plusieurs fois — ou réinitialisez re.lastIndex = 0. Pour parcourir toutes les correspondances, exec() dans une boucle while fonctionne, mais il existe bien plus propre : matchAll(), qu’on voit tout de suite.
✅ Point d’étape — Reproduisez la séquence ci-dessus dans la console. Le deuxième
test()doit renvoyerfalse. Si les trois renvoienttrue, c’est que votre regex n’a pas le flagg— et c’est précisément pourquoig+test()est piégeux.
Étape 4 — match(), matchAll() et les groupes nommés
Côté chaîne, String.match() renvoie soit la première correspondance avec ses groupes (sans g), soit la liste des correspondances entières (avec g, mais sans les groupes). Pour récupérer à la fois toutes les correspondances et leurs groupes nommés, l’outil moderne est String.matchAll() (ES2020), qui renvoie un itérateur. Il exige le flag g — sinon il lève une TypeError.
const texte = "CMD-2026-00428 puis CMD-2025-00031";
const RE = /CMD-(?<annee>\d{4})-(?<seq>\d{5})/g;
for (const m of texte.matchAll(RE)) {
console.log(m.groups.annee, m.groups.seq);
}
// Affiche :
// 2026 00428
// 2025 00031
Chaque m est un objet de correspondance ; m.groups est un objet portant vos groupes nommés. C’est exactement ce qu’il faut pour extraire des champs structurés sans compter les index. Comparé à la boucle while (exec()), matchAll() est plus sûr (pas de lastIndex à gérer) et plus lisible. Retenez-le comme votre méthode par défaut pour « toutes les correspondances avec leurs champs ».
Étape 5 — Remplacer : replace, $<nom> et la fonction
String.replace() remplace la première occurrence ; replaceAll() (ES2021) les remplace toutes (et, comme matchAll, exige le flag g si on lui passe une regex). Dans le texte de remplacement, on peut réinjecter un groupe nommé avec $<nom>, ou un groupe numéroté avec $1.
const ref = "CMD-2026-00428";
ref.replace(/CMD-(?<annee>\d{4})-(?<seq>\d{5})/,
"commande $<seq> de l'année $<annee>");
// → "commande 00428 de l'année 2026"
Plus puissant encore : passer une fonction de remplacement, appelée pour chaque correspondance. Idéal pour transformer une valeur — par exemple formater un montant en FCFA avec les séparateurs de milliers, en réutilisant l’API native plutôt que le motif de l’étape précédente.
const ligne = "Total 1250000 FCFA";
ligne.replace(/\d+(?= FCFA)/, n => Number(n).toLocaleString("fr-FR"));
// → "Total 1 250 000 FCFA"
La fonction reçoit la correspondance (ici n = "1250000") et renvoie la chaîne de remplacement. Le lookahead (?= FCFA) garantit qu’on ne reformate que les vrais montants. C’est l’union du tutoriel sur les assertions et de la puissance de JavaScript.
Étape 6 — Sécuriser une saisie et assembler l’analyseur
Quand un motif intègre du texte saisi par l’utilisateur — une recherche, un filtre — il faut échapper ce texte, sinon un caractère spécial casse le motif ou ouvre une faille. Depuis ES2025, RegExp.escape() fait ça proprement, et il est disponible dans les navigateurs récents et Node à jour.
const saisie = "CMD-2026 (urgent?)";
const re = new RegExp(RegExp.escape(saisie)); // les ( ? deviennent littéraux
// Fallback pour un environnement plus ancien :
function echapper(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
Place maintenant à l’analyseur complet. On compile le motif de découpe une fois, on l’applique à chaque ligne avec match(), on écarte les lignes qui ne correspondent pas, et on récupère les groupes nommés.
const LIGNE = /^(?<ip>\d{1,3}(?:\.\d{1,3}){3}) .* "(?<methode>[A-Z]+) (?<chemin>\/\S*) HTTP\/[\d.]+" (?<statut>\d{3})/;
function analyser(lignes) {
return lignes
.map(l => l.match(LIGNE))
.filter(Boolean)
.map(m => m.groups);
}
const journal = [
'196.46.21.7 - - [28/May/2026:08:14:55 +0000] "GET /commande/428 HTTP/1.1" 200',
'41.82.13.9 - - [28/May/2026:08:15:02 +0000] "POST /paiement HTTP/1.1" 500',
];
console.log(analyser(journal));
// [ {ip:"196.46.21.7", methode:"GET", chemin:"/commande/428", statut:"200"},
// {ip:"41.82.13.9", methode:"POST", chemin:"/paiement", statut:"500"} ]
✅ Point d’étape — Collez ce code dans Node ou la console.
analyser(journal)doit renvoyer deux objets avec les quatre champs remplis. Si un objet estundefined, c’est que.filter(Boolean)manque :match()renvoienullsur une ligne non conforme.
Étape 7 — Découper proprement avec split()
Les regex ne servent pas qu’à chercher ; elles excellent aussi à découper. String.split() accepte une expression régulière comme séparateur, ce qui résout un problème quotidien : des données dont les délimiteurs sont incohérents. Les exports CSV bricolés de Téranga Livraison en sont un bon exemple — parfois un point-virgule, parfois une virgule, avec des espaces qui traînent autour. Une seule regex absorbe toute cette saleté.
const brut = "Awa Diallo; +221771234567 ,Dakar";
const champs = brut.split(/\s*[;,]\s*/);
// → ["Awa Diallo", "+221771234567", "Dakar"]
Le séparateur /\s*[;,]\s*/ se lit : « d’éventuelles espaces, puis un point-virgule ou une virgule, puis d’éventuelles espaces ». En les incluant dans le séparateur, on découpe et on nettoie en une seule passe — pas besoin d’un trim() sur chaque champ ensuite. C’est typiquement le genre de tâche où une chaîne fixe passée à split() échouerait, parce qu’elle ne gérerait qu’un seul séparateur à la fois. Là où un découpage naïf produirait des champs avec des espaces parasites, la regex livre des valeurs propres, directement utilisables.
Petit raffinement utile : si vous voulez limiter le nombre de morceaux, split() accepte un second argument. brut.split(/\s*[;,]\s*/, 2) ne renverra que les deux premiers champs. Et si votre séparateur contient un groupe capturant, JavaScript inclut ce qui a été capturé dans le résultat — une subtilité à connaître quand on veut conserver les délimiteurs eux-mêmes. Entre matchAll() pour extraire et split() pour découper, vous tenez désormais les deux gestes fondamentaux du traitement de texte en JavaScript.
✅ Point d’étape — Testez
"a; b ,c".split(/\s*[;,]\s*/): vous devez obtenir exactement["a", "b", "c"], sans espace résiduel. Si des espaces subsistent, c’est que les\s*autour de la classe[;,]manquent.
🐞 Pièges fréquents
| Symptôme | Cause probable | Correctif |
|---|---|---|
test() alterne true/false sur la même chaîne |
Regex avec flag g : lastIndex avance entre les appels |
Retirer g pour test(), ou remettre re.lastIndex = 0 |
TypeError: matchAll must be called with a global RegExp |
matchAll()/replaceAll() sans flag g |
Ajouter le flag g |
Le motif marche en littéral mais pas en new RegExp |
Antislashs non doublés dans la chaîne | "\\d" au lieu de "\d" |
| Un point ne traverse pas les lignes | Flag s (dotAll) absent |
Ajouter s : /.../s |
🌍 Adaptation au contexte ouest-africain
Tout ce code tourne dans Node.js sur le moindre VPS à quelques milliers de FCFA par mois, ou même directement dans le navigateur du client — aucune dépendance, aucune bibliothèque à installer, donc rien à télécharger sur une connexion lente. Un analyseur de logs comme analyser() peut tourner sur un petit serveur de Dakar pour surveiller les erreurs 500 d’une boutique en temps réel. Et RegExp.escape() protège vos formulaires de recherche contre les saisies malveillantes sans ajouter la moindre dépendance.
✅ Récapitulatif
On crée une regex en littéral /.../ (motif figé) ou avec new RegExp() (motif dynamique, antislashs doublés). Les flags g i m s u y d v règlent le comportement ; g rend test()/exec() stateful via lastIndex. Pour extraire toutes les correspondances avec leurs groupes nommés, matchAll() (avec g) est la voie royale. On remplace avec replace()/replaceAll(), les références $<nom> ou une fonction. Et on échappe toute saisie utilisateur avec RegExp.escape(). Votre fonction analyser() est la preuve que tout cela tient ensemble.
🧾 Aide-mémoire
| Élément | Rôle |
|---|---|
/motif/gi / new RegExp(str, "gi") |
Créer une regex (figée / dynamique) |
re.test(s) |
Booléen : correspond ou non |
s.match(re) / s.matchAll(re) |
Première correspondance / itérateur de toutes (g requis) |
m.groups.nom |
Lire un groupe nommé |
s.replace(re, "$<nom>") |
Remplacer avec un groupe nommé |
s.replace(re, m => ...) |
Remplacer via une fonction |
RegExp.escape(s) |
Échapper une saisie avant injection (ES2025) |
💪 À vous de jouer
Écrivez une fonction compterStatuts(lignes) qui renvoie un objet comptant les occurrences de chaque code de statut HTTP dans le journal (par ex. {"200": 12, "500": 3}).
Voir une solution
function compterStatuts(lignes) {
const compte = {};
for (const ligne of lignes) {
const m = ligne.match(/" (?<statut>\d{3})\s*$/);
if (m) {
const s = m.groups.statut;
compte[s] = (compte[s] || 0) + 1;
}
}
return compte;
}
On capture le statut en fin de ligne, puis on incrémente un compteur par code. Le (compte[s] || 0) initialise proprement la première occurrence.
Tutoriels frères
- Les regex en Python avec le module re — le même analyseur, côté Python.
- Assertions : lookahead, lookbehind et limites de mots — les conditions qu’on injecte dans ces motifs.
Pour aller plus loin
- 🔝 Retour au guide : Maîtriser les expressions régulières : le guide complet
- Référence complète de l’objet RegExp sur MDN (source primaire).
FAQ
Q : Faut-il recompiler la regex à chaque appel ?
R : Non, et c’est même contre-productif. Déclarez vos littéraux une fois (par exemple en constante de module) pour que le moteur les compile une seule fois. Recréer new RegExp() dans une boucle chaude gaspille du temps.
Q : Quelle différence entre match avec g et matchAll ?
R : match(re_g) renvoie un tableau des correspondances entières, sans les groupes. matchAll(re_g) renvoie un itérateur d’objets de correspondance complets, groupes nommés inclus. Dès que vous voulez les groupes, c’est matchAll.
Q : RegExp.escape n’existe pas dans mon environnement, que faire ?
R : Utilisez la fonction echapper() donnée à l’étape 6, qui échappe les caractères spéciaux à la main. Elle est sûre et fonctionne partout, le temps que votre runtime se mette à jour.
Mots-clés : regex JavaScript, RegExp, flags regex, matchAll, groupes nommés JavaScript, replace regex, RegExp.escape, lastIndex, Node.js.