Leçon 5/6 10 min

Groupes, captures et alternance

Les parenthèses : grouper et capturer. Réorganiser une date avec $1 $2 $3, alterner avec | et son piège de précédence.

Deux symboles qui se ressemblent, deux mondes

Rappelle-toi la leçon 2. Tu avais appris les crochets : [abc], c'est UN seul caractère parmi a, b ou c. Une classe, un choix de lettre.

Aujourd'hui on attaque leur cousin, les parenthèses : (abc). Et là, attention, ça ne veut pas du tout dire la même chose. (abc), c'est la séquence « abc » prise comme un bloc. Pas un caractère parmi, mais trois caractères collés qu'on traite ensemble.

Ce bloc, les parenthèses, va te servir à faire deux choses en même temps. D'abord grouper, pour quantifier un morceau entier d'un coup. Ensuite capturer, c'est-à-dire mémoriser le morceau attrapé pour le réutiliser. C'est la capture qui transforme une regex « qui dit oui/non » en outil « qui découpe et réécrit ». On va y arriver, et c'est le moment magique du cours.

Grouper : quantifier un morceau entier

Premier rôle des parenthèses : décider sur quoi porte un quantificateur (le +, le *, le ? de la leçon 3). Sans elles, un + ne s'applique qu'au caractère juste avant lui.

Compare. ha+ veut dire « un h, puis un ou plusieurs a » : ça matche haaa, mais pas haha. Maintenant (ha)+ veut dire « un ou plusieurs blocs ha » : ça matche ha, haha, hahaha. La parenthèse a changé l'unité que le + répète.

À retenir : un quantificateur s'accroche au dernier élément à sa gauche : un caractère, une classe […], ou un groupe (…). Si tu veux répéter plus d'un caractère, tu dois le mettre dans des parenthèses.

À toi de le sentir dans le labo. Tu veux un motif qui valide une suite de blocs ha rien que des blocs ha, du début à la fin. Souviens-toi des ancres de la leçon 4 : ici elles sont indispensables pour rejeter tout ce qui dépasse.

🎯 Labo regex · groupe + quantificateur
//
Voir la solution

Le motif est ^(ha)+$. Le groupe (ha) fait du bloc « ha » l'unité répétée par le +. Les ancres ^ et $ obligent la chaîne entière à n'être que des « ha » : hxha contient un x qui dépasse, et ha h a contient des espaces : les deux sont rejetés. Sans les ancres, (ha)+ retrouverait quand même un « ha » à l'intérieur de hxha et le validerait à tort.

Capturer : mémoriser le morceau attrapé

Voici le deuxième rôle, et le plus puissant. Chaque parenthèse ne fait pas que grouper : elle capture. Le compteur démarre à 1 et avance à chaque parenthèse ouvrante, de gauche à droite. Le bout de texte attrapé par le premier groupe devient $1, le deuxième $2, et ainsi de suite.

Le cas star, celui que tu réutiliseras toute ta vie : la date. Prends 2026-06-04 et le motif :

/(\d{4})-(\d{2})-(\d{2})/

Trois groupes. Le groupe 1, (\d{4}), attrape l'année 2026. Le groupe 2, le mois 06. Le groupe 3, le jour 04. Tu ne testes plus juste « est-ce une date ? » : tu as découpé la date en trois morceaux nommés $1, $2, $3.

Et maintenant la magie. Dans un remplacement, tu réécris le texte en réordonnant ces morceaux. Pour passer du format ISO aaaa-mm-jj au format français jj/mm/aaaa, tu écris comme remplacement :

'$3/$2/$1'

Résultat : 04/06/2026. Tu viens de réorganiser une date avec une seule expression régulière. C'est exactement ça, capturer : attraper des morceaux pour les replacer ailleurs.

Le labo passe en mode remplacement. En haut, ton motif à groupes. En bas, ton remplacement. Le labo te montre en direct le résultat de input.replace(motif, remplacement). La source est 2026-06-04 : écris le motif qui capture les trois morceaux, et le remplacement $3/$2/$1.

🎯 Labo regex · capturer et réordonner une date
//
Voir la solution

Le motif est (\d{4})-(\d{2})-(\d{2}). Le {2} sur le mois et le jour est ce qui rejette 2026-6-4 : il n'y a qu'un seul chiffre, pas deux. Avec le remplacement $3/$2/$1, le labo affiche « Résultat : 04/06/2026 ». Le groupe 1 (année) atterrit en dernier, le groupe 3 (jour) en premier : tu as inversé l'ordre sans toucher au texte.

Prédis avant de lire

Tu appliques le remplacement $1/$2/$3 (au lieu de $3/$2/$1) sur 2026-06-04 avec le motif (\d{4})-(\d{2})-(\d{2}). Qu'obtiens-tu ?

Voir la réponse

2026/06/04. Le moteur remet les morceaux dans l'ordre que TU écris : $1 (l'année) d'abord, $2 (le mois) ensuite, $3 (le jour) en dernier. Comme c'est le même ordre que la capture, tu n'as fait que remplacer les tirets par des slashs. Pour vraiment inverser en date française, il faut écrire $3/$2/$1. La leçon : l'ordre des morceaux dans le remplacement, c'est toi qui le décides, indépendamment de l'ordre où ils ont été capturés.

L'alternance : choisir entre plusieurs options

Le tube | veut dire « OU ». chien|chat|oiseau matche chien, ou chat, ou oiseau. C'est l'équivalent, pour des mots entiers, de ce que [abc] faisait pour des lettres seules.

Mais le | a une portée énorme, et c'est là que tout le monde se fait avoir. Il s'étend jusqu'aux bornes de son groupe, ou à défaut jusqu'aux bords du motif. Sans parenthèses, il avale tout ce qui l'entoure.

Le piège de précédence du | : tu veux valider une chaîne qui est exactement « chien » ou exactement « chat ». Tu écris naïvement /^chien|chat$/. Ça ne fait PAS ce que tu crois. Le moteur lit (^chien) OU (chat$) : soit « commence par chien », soit « finit par chat ». Du coup mon chat passe (il finit par chat), et chien méchant passe aussi (il commence par chien). La solution : grouper l'alternance pour que les ancres encadrent les DEUX options. Écris /^(chien|chat)$/ : là, la chaîne entière doit valoir « chien » ou « chat », rien d'autre.

Grouper sans capturer : (?:...)

Parfois tu veux grouper juste pour quantifier ou pour cadrer une alternance, mais tu n'as aucun besoin de récupérer le morceau ensuite. Capturer pour rien, ça gaspille un numéro de groupe et ça décale tous les $1, $2 suivants.

D'où le groupe non capturant : (?:...). Le ?: juste après la parenthèse ouvrante dit « groupe, mais ne mémorise pas ». (?:ha)+ matche pareil que (ha)+, mais sans occuper de case $1.

Le pourquoi concret : dans une regex avec plusieurs groupes, garder tes numéros propres. Si seuls deux morceaux t'intéressent vraiment, mets-les en groupes capturants et passe tout le reste en (?:...). Tes $1 et $2 pointeront alors exactement sur ce que tu veux, sans avoir à compter des groupes parasites.

Au labo, tu valides les URL en http:// et https://. Le s est optionnel : c'est un cas pour le ? de la leçon 3, mais il ne doit porter que sur le s, pas sur tout le reste.

🎯 Labo regex · http et https
//
Voir la solution

Le motif le plus direct est https?:// : le ? ne porte que sur le s juste à sa gauche, donc le s est optionnel. ftp:// n'a ni http ni https au départ, il est rejeté. Tu peux aussi grouper sans capturer : (?:https?):// matche pareil, et tu te sers du groupe non capturant quand tu veux cadrer un bloc sans gaspiller de numéro de capture. (Pense à échapper les / ou à utiliser un autre délimiteur selon ton langage.)

Two look-alike symbols, two worlds

Remember lesson 2. You learned the square brackets: [abc] is ONE single character among a, b or c. A class, a choice of letter.

Today we tackle their cousin, the parentheses: (abc). And watch out, it means something completely different. (abc) is the sequence "abc" taken as a block. Not one character among several, but three characters glued together and handled as a unit.

That block, the parentheses, lets you do two things at once. First, group, to quantify a whole chunk in one go. Second, capture, that is remember the matched chunk to reuse it. Capturing is what turns a yes/no regex into a tool that slices and rewrites. We'll get there, and it's the magic moment of the course.

Grouping: quantifying a whole chunk

First role of parentheses: deciding what a quantifier applies to (the +, *, ? from lesson 3). Without them, a + only applies to the single character right before it.

Compare. ha+ means "an h, then one or more a": it matches haaa, but not haha. Now (ha)+ means "one or more ha blocks": it matches ha, haha, hahaha. The parenthesis changed the unit that + repeats.

Keep in mind: a quantifier attaches to the last element on its left: a character, a class […], or a group (…). If you want to repeat more than one character, you must wrap it in parentheses.

Feel it in the lab. You want a pattern that validates a run of ha blocks and only ha blocks, from start to end. Recall the anchors from lesson 4: here they're essential to reject anything that sticks out.

🎯 Regex lab · group + quantifier
//
Show the solution

The pattern is ^(ha)+$. The group (ha) makes the "ha" block the unit repeated by +. The anchors ^ and $ force the whole string to be nothing but "ha": hxha has a stray x, and ha h a has spaces, so both are rejected. Without the anchors, (ha)+ would still find a "ha" inside hxha and validate it by mistake.

Capturing: remembering the matched chunk

Here's the second role, the most powerful one. Each parenthesis doesn't just group: it captures. The counter starts at 1 and advances at every opening parenthesis, left to right. The text matched by the first group becomes $1, the second $2, and so on.

The star case, the one you'll reuse your whole life: the date. Take 2026-06-04 and the pattern:

/(\d{4})-(\d{2})-(\d{2})/

Three groups. Group 1, (\d{4}), grabs the year 2026. Group 2, the month 06. Group 3, the day 04. You're no longer just testing "is this a date?": you've sliced the date into three named chunks $1, $2, $3.

Now the magic. In a replacement, you rewrite the text by reordering those chunks. To go from ISO format yyyy-mm-dd to the French format dd/mm/yyyy, you write as the replacement:

'$3/$2/$1'

Result: 04/06/2026. You just rearranged a date with a single regular expression. That's exactly what capturing is: grabbing chunks to place them elsewhere.

The lab switches to replace mode. On top, your pattern with groups. Below, your replacement. The lab shows you live the result of input.replace(pattern, replacement). The source is 2026-06-04: write the pattern that captures the three chunks, and the replacement $3/$2/$1.

🎯 Regex lab · capture and reorder a date
//
Show the solution

The pattern is (\d{4})-(\d{2})-(\d{2}). The {2} on the month and day is what rejects 2026-6-4: it has only one digit, not two. With the replacement $3/$2/$1, the lab shows "Result: 04/06/2026". Group 1 (year) lands last, group 3 (day) first: you reversed the order without touching the text.

Predict before reading on

You apply the replacement $1/$2/$3 (instead of $3/$2/$1) on 2026-06-04 with the pattern (\d{4})-(\d{2})-(\d{2}). What do you get?

Show the answer

2026/06/04. The engine puts the chunks back in the order YOU write: $1 (the year) first, $2 (the month) next, $3 (the day) last. Since that's the same order as the capture, all you did was replace the dashes with slashes. To truly flip it into a French date, you need $3/$2/$1. The lesson: the order of chunks in the replacement is yours to decide, independent of the order they were captured in.

Alternation: choosing between several options

The pipe | means "OR". chien|chat|oiseau matches chien, or chat, or oiseau. For whole words, it's the equivalent of what [abc] did for single letters.

But the | has a huge reach, and that's where everyone gets caught. It extends to the bounds of its group, or failing that to the edges of the pattern. Without parentheses, it swallows everything around it.

The | precedence trap: you want to validate a string that is exactly "chien" or exactly "chat". You naively write /^chien|chat$/. It does NOT do what you think. The engine reads (^chien) OR (chat$): either "starts with chien" or "ends with chat". So mon chat passes (it ends with chat), and chien méchant passes too (it starts with chien). The fix: group the alternation so the anchors wrap BOTH options. Write /^(chien|chat)$/: now the whole string must equal "chien" or "chat", nothing else.

Grouping without capturing: (?:...)

Sometimes you group just to quantify or to frame an alternation, but you have no need to retrieve the chunk afterwards. Capturing for nothing wastes a group number and shifts all the following $1, $2.

Hence the non-capturing group: (?:...). The ?: right after the opening parenthesis says "group, but don't remember". (?:ha)+ matches just like (ha)+, but without taking up a $1 slot.

The concrete why: in a regex with several groups, keep your numbers clean. If only two chunks really matter to you, make them capturing groups and turn everything else into (?:...). Your $1 and $2 will then point exactly at what you want, without counting parasitic groups.

In the lab, you validate URLs in http:// and https://. The s is optional: a case for the ? from lesson 3, but it must apply only to the s, not to everything else.

🎯 Regex lab · http and https
//
Show the solution

The most direct pattern is https?://: the ? only applies to the s right to its left, so the s is optional. ftp:// has neither http nor https at the start, so it's rejected. You can also group without capturing: (?:https?):// matches the same, and you reach for the non-capturing group when you want to frame a block without wasting a capture number. (Remember to escape the / or use another delimiter depending on your language.)

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Explique à un collègue les DEUX rôles des parenthèses dans une regex, avec un exemple pour chacun.

Rôle 1, grouper : faire d'un morceau l'unité d'un quantificateur, ex. (ha)+ répète le bloc « ha », pas juste le « a ». Rôle 2, capturer : mémoriser le morceau attrapé pour le réutiliser, ex. (\d{4})-(\d{2})-(\d{2}) attrape année, mois, jour dans $1 $2 $3 et permet de réécrire en $3/$2/$1. Et (?:...) groupe sans capturer.
🧠 Rappel libre
Rappel libre

Sans remonter : à quoi sert (?:...) et en quoi diffère-t-il de (...) ?

(?:...) groupe sans capturer : il sert à quantifier ou à cadrer une alternance, mais il n'occupe aucun numéro de groupe. (...), lui, capture le morceau dans $1, $2… Utiliser (?:...) quand tu n'as pas besoin du morceau garde tes numéros de capture propres et alignés sur ce qui t'intéresse vraiment.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

Tu demandes à l'IA de convertir tes dates ISO 2026-06-04 au format français jj/mm/aaaa. Elle répond : « Voilà : date.replace(/(\d{4})-(\d{2})-(\d{2})/, '$1/$2/$3') ». Tu testes : ça sort 2026/06/04. Tu acceptes, ou tu rejettes ?

À rejeter : le motif est bon, mais l'ordre des groupes dans le remplacement est faux. $1/$2/$3 garde l'année en premier et ne fait que remplacer les tirets par des slashs, d'où 2026/06/04. Pour une vraie date française jj/mm/aaaa, il faut inverser : '$3/$2/$1' donne 04/06/2026. Le motif capture dans un ordre, mais c'est TOI qui choisis l'ordre de réécriture.
Quelle est la différence entre [abc] et (abc) ?
Avec le motif (\d{4})-(\d{2})-(\d{2}) sur 2026-06-04, que vaut le remplacement $3/$2/$1 ?
Tu veux grouper un morceau pour le quantifier, mais tu n'as pas besoin de le récupérer. Quel groupe utilises-tu ?
Tu veux valider une chaîne qui vaut EXACTEMENT « chien » ou « chat ». Tu écris /^chien|chat$/. Pourquoi mon chat passe-t-il quand même, et comment corriger ?
Prochaine étape

Tu sais tout faire dans le labo : grouper, capturer, réordonner, choisir. Dernière leçon : dans TON code. test, match, replace, les flags, et deux vérités qui fâchent sur l'email et le HTML.

Leçon 6 : Regex en JavaScript pour de vrai →

Une erreur dans cette leçon, un passage flou, une question ? Écrivez-moi : chaque retour améliore ce cours.

Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement