Développement Web

Groupes, captures et alternation en regex

12 min de lecture

Au tutoriel précédent, le motif CMD-\d{4}-\d{5} savait repérer une référence de commande de Téranga Livraison. Mais repérer ne suffit pas : pour alimenter un tableau de bord, il faut extraire l’année et le numéro de séquence séparément. C’est tout l’objet des groupes de capture. On va aussi apprendre à offrir plusieurs formes au même motif — un numéro de téléphone sénégalais ou ivoirien — grâce à l’alternation.

📍 Article principal du parcours : Maîtriser les expressions régulières : le guide complet
Ce tutoriel fait partie du parcours « Expressions régulières ». Lisez d’abord le guide pour l’ordre conseillé.

🎯 Ce que vous allez apprendre

  • Capturer des sous-parties d’une correspondance avec les parenthèses (...) et les relire par leur numéro.
  • Nommer vos captures pour un code lisible, et connaître la différence de syntaxe entre JavaScript et Python.
  • Distinguer un groupe capturant d’un groupe non capturant (?:...) et savoir quand préférer le second.
  • Offrir plusieurs variantes au même motif avec l’alternation |, et détecter un mot dupliqué avec une rétroréférence.

🛠️ Ce que vous allez construire

Un motif qui découpe CMD-2026-00428 en deux champs nommés — l’année et le numéro — directement exploitables, et un motif d’alternation qui reconnaît un numéro de téléphone quel que soit son indicatif ouest-africain. Ce sont les briques d’extraction que les versions JavaScript et Python du parcours réutiliseront telles quelles.

Prérequis

  • Avoir suivi le tutoriel Les fondamentaux des regex ou en maîtriser le contenu (classes, quantificateurs, ancres).
  • Un bac à sable comme regex101.com pour visualiser les groupes capturés dans le panneau de droite.
  • Niveau : débutant à l’aise. ⏱️ ~30 minutes.

Étape 1 — Capturer avec des parenthèses

Entourer une partie du motif de parenthèses crée un groupe de capture : le moteur mémorise séparément ce qui a été reconnu à cet endroit. Les groupes sont numérotés de gauche à droite, en commençant à 1, dans l’ordre de leurs parenthèses ouvrantes. Reprenons la référence de commande et isolons l’année et la séquence.

CMD-(\d{4})-(\d{5})

Sur "CMD-2026-00428" :
  groupe 0 → CMD-2026-00428   (toujours la correspondance entière)
  groupe 1 → 2026             (le premier (...))
  groupe 2 → 00428            (le second (...))

Le groupe 0 est une convention : il désigne toujours la totalité de ce que le motif a reconnu. Les groupes 1 et 2 sont nos champs utiles. Dans n’importe quel langage, on les récupère ensuite par leur index — on le verra concrètement dans les tutoriels JavaScript et Python. Pour l’instant, retenez le principe : une paire de parenthèses = un champ extractible.

Point d’étape — Collez CMD-(\d{4})-(\d{5}) dans regex101 avec le texte « CMD-2026-00428 ». Le panneau de droite (« Match Information ») doit lister Group 1 = 2026 et Group 2 = 00428. Si vous ne voyez qu’un seul groupe, vérifiez que vos deux paires de parenthèses sont bien fermées.

Étape 2 — Nommer ses captures

Compter les parenthèses pour retrouver « le groupe 2 » devient vite pénible et fragile : ajoutez un groupe au milieu, et toute la numérotation se décale. Les groupes nommés résolvent ça en attachant une étiquette parlante à chaque capture. C’est là qu’apparaît une divergence de syntaxe qu’il faut connaître une bonne fois.

JavaScript, PCRE, Java, .NET :  CMD-(?<annee>\d{4})-(?<sequence>\d{5})
Python (module re)           :  CMD-(?P<annee>\d{4})-(?P<sequence>\d{5})

La seule différence est le P qu’exige Python juste après le point d’interrogation. Le reste est identique : un chevron ouvrant, le nom, un chevron fermant, puis le sous-motif. Une fois nommés, vos champs se lisent dans le code par annee et sequence au lieu de 1 et 2 — un gain de lisibilité énorme dès qu’un motif dépasse deux ou trois groupes. Attention : le module re de Python n’accepte que la forme (?P<nom>...), quelle que soit la version ; la forme sans P y déclenche une erreur. Inversement, JavaScript n’accepte que (?<nom>...). C’est une source de confusion classique quand on passe d’un langage à l’autre.

Point d’étape — Dans regex101, choisissez la saveur « ECMAScript » et testez CMD-(?<annee>\d{4})-(?<sequence>\d{5}). Le panneau de droite doit afficher les groupes par leur nom. Basculez sur la saveur « Python » avec le même motif : regex101 signale une erreur, car Python réclame le P. Réécrivez-le en CMD-(?P<annee>\d{4})-(?P<sequence>\d{5}) et la correspondance revient.

Étape 3 — Groupes non capturants

Parfois, on a besoin de parenthèses uniquement pour grouper — par exemple appliquer un quantificateur à un bloc — sans pour autant vouloir mémoriser le contenu. Capturer pour rien gaspille de la mémoire et brouille la numérotation. Le groupe non capturant (?:...) groupe sans capturer.

Imaginons que la référence puisse être précédée d’un préfixe optionnel « REF » répété, comme dans « REFREF-CMD-2026-00428 ». On veut autoriser zéro, une ou deux occurrences de « REF », sans en faire un champ :

(?:REF){0,2}-?CMD-(?<annee>\d{4})-(?<sequence>\d{5})

Ici (?:REF){0,2} dit « le bloc REF, zéro à deux fois », mais comme il est non capturant, nos seuls groupes nommés restent annee et sequence. La règle pratique : capturez ce que vous comptez relire, groupez sans capturer tout le reste. Vos motifs gagnent en clarté et vos accès aux groupes restent stables.

Étape 4 — L’alternation : plusieurs formes, un seul motif

Les coursiers de Téranga Livraison enregistrent des numéros de plusieurs pays. Un numéro sénégalais commence par l’indicatif +221, un ivoirien par +225, un malien par +223. L’alternation, notée par la barre verticale |, exprime un choix : « ceci ou cela ». Combinée à un groupe, elle reconnaît n’importe lequel de ces indicatifs.

\+(?:221|225|223)(?:\s?\d{2,3})+

Reconnaît :  +221 77 123 45 67   (tranches 77 123 45 67)
             +225 07 12 34 56     (tranches 07 12 34 56)

Décortiquons : \+ est un plus littéral (le plus est un métacaractère, on l’échappe) ; (?:221|225|223) propose les trois indicatifs en groupe non capturant ; puis (?:\s?\d{2,3})+ répète « une espace optionnelle suivie d’une tranche de deux ou trois chiffres » autant de fois qu’il en faut. Cette tolérance est volontaire : elle absorbe aussi bien le découpage sénégalais (77 123 45 67) que l’ivoirien (07 12 34 56), sans qu’on ait à figer le nombre exact de chiffres. L’alternation a la priorité la plus basse : sans les parenthèses, \+221|225|223 signifierait « +221 ou 225 ou 223 » — pas du tout la même chose. Encadrer une alternation par un groupe est presque toujours nécessaire.

Point d’étape — Testez le motif sur la liste « +221 77 123 45 67 / +225 07 12 34 56 78 / +33 6 12 34 56 78 ». Les deux premiers numéros doivent correspondre, le troisième non — son indicatif 33 ne figure pas dans (?:221|225|223). Astuce de débogage : si vous retirez les parenthèses, \+221|225|223… se met à reconnaître un « 225 » ou « 223 » isolé n’importe où dans le texte — la preuve qu’encadrer le choix est indispensable.

Étape 5 — Rétroréférences et vérification finale

Une rétroréférence (backreference) permet de réutiliser, plus loin dans le même motif, ce qu’un groupe a déjà capturé. C’est l’outil pour exiger qu’une chose se répète à l’identique. Cas concret : repérer une coquille classique dans les logs, un mot doublé comme « la la commande » ou « GET GET ».

\b(\w+)\s+\1\b

\1 rappelle « exactement ce que le groupe 1 a capturé »
Reconnaît :  "GET GET"  ou  "la la"
Ignore     :  "GET POST"

Le groupe (\w+) capture un mot, \s+ avale les espaces, et \1 exige le même mot juste après. Pour une rétroréférence à un groupe nommé, la syntaxe diffère encore selon le langage : \k<nom> en JavaScript, (?P=nom) en Python. Attention : toutes les saveurs ne gèrent pas les rétroréférences — le moteur par défaut de l’outil ripgrep, par exemple, les refuse pour garantir sa rapidité. On détaillera ce point dans le tutoriel en ligne de commande.

Assemblons : nous savons maintenant capturer (l’année et la séquence d’une commande), nommer ces captures, grouper sans capturer, choisir entre plusieurs formes, et exiger une répétition. Le motif CMD-(?<annee>\d{4})-(?<sequence>\d{5}) est désormais prêt à livrer des champs structurés à n’importe quel programme.

Étape 6 — Assembler : découper une ligne de log entière

Les groupes prennent toute leur valeur quand on en combine plusieurs pour découper une ligne complète en champs nommés. Reprenons la ligne d’access log de Téranga Livraison et capturons d’un coup l’adresse IP, la méthode HTTP, le chemin demandé et le code de statut. Chaque champ devient un groupe nommé, prêt à remplir une colonne de tableau.

^(?<ip>\d{1,3}(?:\.\d{1,3}){3}) .* "(?<methode>[A-Z]+) (?<chemin>/\S*) HTTP/[\d.]+" (?<statut>\d{3})

Sur :  196.46.21.7 - - [28/May/2026:08:14:55 +0000] "GET /commande/428 HTTP/1.1" 200
  ip      → 196.46.21.7
  methode → GET
  chemin  → /commande/428
  statut  → 200

Notez l’astuce dans (?<ip>\d{1,3}(?:\.\d{1,3}){3}) : au lieu de répéter quatre fois le bloc « point + chiffres », on capture le premier octet, puis on répète (?:\.\d{1,3}) exactement trois fois grâce au quantificateur {3} sur un groupe non capturant. Le motif est plus court et plus lisible — exactement le genre de simplification que les groupes permettent. Le .* entre l’IP et le premier guillemet saute les champs qu’on ne veut pas (les tirets, l’horodatage), parce qu’on les capturera autrement dans un autre tutoriel.

Un détail piège, à connaître absolument : un groupe placé sous quantificateur ne conserve que sa dernière capture. Dans (?:\.\d{1,3}){3}, on a justement rendu le groupe non capturant, donc la question ne se pose pas. Mais si vous écrivez (\d+,)+ sur la chaîne « 12,34,56, », le groupe 1 ne contiendra que « 56, », pas la liste entière. Pour récupérer toutes les occurrences, il faut soit capturer le bloc répété d’un coup (((?:\d+,)+)), soit itérer côté code avec une recherche globale — une distinction qu’on exploitera dans les tutoriels JavaScript et Python.

Point d’étape — Le motif de découpe ci-dessus, testé sur une vraie ligne de log, doit remplir les quatre groupes nommés ip, methode, chemin, statut. Si chemin est vide, vérifiez l’espace entre la méthode et le chemin dans votre motif — un espace manquant casse tout le découpage.

🐞 Pièges fréquents

Symptôme Cause probable Correctif
L’alternation attrape trop ou trop peu Pas de parenthèses : | a la priorité la plus basse et s’étend jusqu’aux bords du motif Encadrer le choix : (?:a|b|c)
« unknown extension ?< » en Python Syntaxe JavaScript (?<nom>...) utilisée avec le module re En Python, toujours (?P<nom>...)
Les numéros de groupe sont décalés Un groupe capturant a été ajouté au milieu Passer en groupes nommés, insensibles au décalage
La rétroréférence \1 est ignorée Moteur sans backreferences (regex « Rust » de ripgrep, par ex.) Activer PCRE2 (grep -P, rg -P) — voir le tuto ligne de commande

🌍 Adaptation au contexte ouest-africain

Les formats de numéros varient d’un opérateur et d’un pays à l’autre : indicatifs +221 (Sénégal), +225 (Côte d’Ivoire), +223 (Mali), +226 (Burkina), +229 (Bénin), avec ou sans espaces, parfois précédés d’un 00 au lieu du +. L’alternation et les groupes optionnels sont exactement les outils pour absorber cette diversité sans écrire dix motifs. Construisez votre liste d’indicatifs une fois, regroupez-la dans un (?:...), et réutilisez ce bloc dans tous vos projets — un petit investissement qui paie sur chaque formulaire d’inscription.

✅ Récapitulatif

Les parenthèses (...) capturent des champs numérotés à partir de 1 ; (?<nom>...) (ou (?P<nom>...) en Python) les nomme pour un code robuste ; (?:...) groupe sans capturer ; | exprime un choix qu’il faut presque toujours encadrer ; et \1 (ou \k<nom>) rappelle une capture pour exiger une répétition. Vous tenez les briques d’extraction du toolkit de Téranga Livraison.

🧾 Aide-mémoire

Élément Signification
(...) Groupe capturant, numéroté à partir de 1
(?<nom>...) Groupe nommé (JS, PCRE, Java, .NET)
(?P<nom>...) Groupe nommé, forme portable Python
(?:...) Groupe non capturant (groupe sans mémoriser)
a|b|c Alternation : a ou b ou c
\1, \2 Rétroréférence à un groupe numéroté
\k<nom> / (?P=nom) Rétroréférence à un groupe nommé (JS / Python)

💪 À vous de jouer

Écrivez un motif qui capture, dans une ligne de log de la forme "GET /commande/428 HTTP/1.1", la méthode HTTP (GET, POST, PUT ou DELETE) dans un groupe nommé methode et le chemin dans un groupe nommé chemin.

Voir une solution

"(?<methode>GET|POST|PUT|DELETE)\s(?<chemin>/\S*)\s
Le groupe methode propose les quatre verbes par alternation ; \s sépare ; chemin capture une barre oblique suivie de tout caractère non-espace (\S*). En Python, écrivez (?P<methode>...) et (?P<chemin>...) — le P y est obligatoire.

Tutoriels frères

Pour aller plus loin

FAQ

Q : Capturer ralentit-il le motif ?
R : Très marginalement, car le moteur mémorise chaque groupe capturant. Sur de gros volumes, remplacer les groupes inutiles par des (?:...) est une micro-optimisation propre, surtout utile quand on répète un motif des millions de fois.

Q : Peut-on imbriquer des groupes ?
R : Oui. La numérotation suit l’ordre des parenthèses ouvrantes, de l’extérieur vers l’intérieur. ((\d{4})-(\d{2})) donne le groupe 1 pour l’ensemble, 2 pour l’année, 3 pour le mois. Les groupes nommés rendent cette imbrication beaucoup plus lisible.

Q : Pourquoi mon alternation préfère-t-elle la première variante ?
R : Le moteur essaie les alternatives de gauche à droite et s’arrête à la première qui marche. Si « a » est un préfixe de « ab », placez la variante la plus longue d’abord : (?:ab|a), sinon « ab » ne sera jamais atteint.

Mots-clés : groupes de capture regex, groupes nommés, alternation, groupe non capturant, rétroréférence, backreference, (?P<nom>), extraction de champs.

Partager