Qu'est-ce qu'un groupe non capturant dans les expressions régulières?


Réponses:


2331

Permettez-moi d'essayer d'expliquer cela avec un exemple.

Considérez le texte suivant:

http://stackoverflow.com/
/programming/tagged/regex

Maintenant, si j'applique l'expression régulière ci-dessous dessus ...

(https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?

... j'obtiendrais le résultat suivant:

Match "http://stackoverflow.com/"
     Group 1: "http"
     Group 2: "stackoverflow.com"
     Group 3: "/"

Match "/programming/tagged/regex"
     Group 1: "https"
     Group 2: "stackoverflow.com"
     Group 3: "/questions/tagged/regex"

Mais je ne me soucie pas du protocole - je veux juste l'hôte et le chemin de l'URL. Donc, je modifie l'expression régulière pour inclure le groupe non capturant (?:).

(?:https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?

Maintenant, mon résultat ressemble à ceci:

Match "http://stackoverflow.com/"
     Group 1: "stackoverflow.com"
     Group 2: "/"

Match "/programming/tagged/regex"
     Group 1: "stackoverflow.com"
     Group 2: "/questions/tagged/regex"

Voir? Le premier groupe n'a pas été capturé. L'analyseur l'utilise pour faire correspondre le texte, mais l'ignore plus tard, dans le résultat final.


ÉDITER:

Comme demandé, permettez-moi d'essayer d'expliquer les groupes aussi.

Eh bien, les groupes ont plusieurs objectifs. Ils peuvent vous aider à extraire des informations exactes d'une correspondance plus importante (qui peut également être nommée), ils vous permettent de faire correspondre un groupe correspondant précédent et peuvent être utilisés pour des substitutions. Essayons quelques exemples, d'accord?

Imaginez que vous ayez une sorte de XML ou HTML (sachez que regex n'est peut-être pas le meilleur outil pour le travail , mais c'est bien comme exemple). Vous voulez analyser les balises, vous pouvez donc faire quelque chose comme ça (j'ai ajouté des espaces pour le rendre plus facile à comprendre):

   \<(?<TAG>.+?)\> [^<]*? \</\k<TAG>\>
or
   \<(.+?)\> [^<]*? \</\1\>

Le premier regex a un groupe nommé (TAG), tandis que le second utilise un groupe commun. Les deux expressions rationnelles font la même chose: elles utilisent la valeur du premier groupe (le nom de la balise) pour correspondre à la balise de fermeture. La différence est que le premier utilise le nom pour correspondre à la valeur et le second utilise l'index de groupe (qui commence à 1).

Essayons maintenant quelques substitutions. Considérez le texte suivant:

Lorem ipsum dolor sit amet consectetuer feugiat fames malesuada pretium egestas.

Maintenant, utilisons cette expression muette dessus:

\b(\S)(\S)(\S)(\S*)\b

Cette expression régulière correspond aux mots d'au moins 3 caractères et utilise des groupes pour séparer les trois premières lettres. Le résultat est le suivant:

Match "Lorem"
     Group 1: "L"
     Group 2: "o"
     Group 3: "r"
     Group 4: "em"
Match "ipsum"
     Group 1: "i"
     Group 2: "p"
     Group 3: "s"
     Group 4: "um"
...

Match "consectetuer"
     Group 1: "c"
     Group 2: "o"
     Group 3: "n"
     Group 4: "sectetuer"
...

Donc, si nous appliquons la chaîne de substitution:

$1_$3$2_$4

... par-dessus, nous essayons d'utiliser le premier groupe, d'ajouter un trait de soulignement, d'utiliser le troisième groupe, puis le deuxième groupe, d'ajouter un autre trait de soulignement, puis le quatrième groupe. La chaîne résultante serait comme celle ci-dessous.

L_ro_em i_sp_um d_lo_or s_ti_ a_em_t c_no_sectetuer f_ue_giat f_ma_es m_la_esuada p_er_tium e_eg_stas.

Vous pouvez également utiliser des groupes nommés pour les substitutions à l'aide de ${name}.

Pour jouer avec les regex, je recommande http://regex101.com/ , qui offre une bonne quantité de détails sur le fonctionnement des regex; il propose également quelques moteurs regex parmi lesquels choisir.


3
@ajsie: Les groupes traditionnels (capture) sont plus utiles si vous effectuez une opération de remplacement sur les résultats. Voici un exemple où j'attrape des
Steve Wortham

2
Non, ce n'est pas pareil.
Ricardo Nolde

4
Pourrait également souligner que les groupes non capturants sont particulièrement utiles lors de l'utilisation de regex comme délimiteurs fractionnés: "Alice et Bob" -split "\ s + (?: Et | ou) \ s +"
Yevgeniy

7
Il serait intéressant d'expliquer la différence entre les groupes non capturants (? :) et les assertions d'anticipation et de lookbehind (? =,?!). Je viens de commencer à apprendre les expressions régulières, mais d'après ce que je comprends, les groupes non capturants sont utilisés pour faire correspondre et "renvoyer" ce qu'ils correspondent, mais cette "valeur de retour" n'est pas "stockée" pour le référencement. D'un autre côté, les assertions Lookahead et lookbehind ne sont pas seulement "stockées", elles ne font pas non plus partie d'une correspondance, elles affirment simplement que quelque chose correspondrait, mais leur valeur "match" est ignorée, si je ne me trompe pas .. (Ai-je à peu près raison?)
Christian

5
[] est un ensemble; [123] correspond à tout caractère à l'intérieur de l'ensemble une fois; [^ 123] correspond une fois à tout ce qui N'EST PAS dans l'ensemble; [^ / \ r \ n] + correspond à un ou plusieurs caractères différents de /, \ r, \ n.
Ricardo Nolde

180

Vous pouvez utiliser des groupes de capture pour organiser et analyser une expression. Un groupe qui ne capture pas a le premier avantage, mais n'a pas les frais généraux du second. Vous pouvez toujours dire qu'un groupe non capturant est facultatif, par exemple.

Supposons que vous vouliez faire correspondre le texte numérique, mais certains nombres pourraient être écrits comme 1er, 2e, 3e, 4e, ... Si vous voulez capturer la partie numérique, mais pas le suffixe (facultatif), vous pouvez utiliser un groupe non capturant .

([0-9]+)(?:st|nd|rd|th)?

Cela correspondra aux nombres sous la forme 1, 2, 3 ... ou sous la forme 1er, 2e, 3e, ... mais il ne capturera que la partie numérique.


3
Concis et probablement la meilleure explication ici.
NelsonGon

107

?: est utilisé lorsque vous souhaitez regrouper une expression, mais que vous ne souhaitez pas l'enregistrer en tant que partie correspondante / capturée de la chaîne.

Un exemple serait quelque chose pour correspondre à une adresse IP:

/(?:\d{1,3}\.){3}\d{1,3}/

Notez que je ne me soucie pas de sauvegarder les 3 premiers octets, mais le (?:...)regroupement me permet de raccourcir l'expression régulière sans encourir la surcharge de capture et de stockage d'une correspondance.


38

Cela rend le groupe non capturant, ce qui signifie que la sous-chaîne mise en correspondance par ce groupe ne sera pas incluse dans la liste des captures. Un exemple en rubis pour illustrer la différence:

"abc".match(/(.)(.)./).captures #=> ["a","b"]
"abc".match(/(?:.)(.)./).captures #=> ["b"]

Pourquoi ne pouvons-nous pas simplement utiliser "abc" .match (/.(.)./). Captures ici?
PRASANNA SARAF

@PRASANNASARAF Vous pouvez, bien sûr. Le but du code était de montrer que (?:)cela ne produit pas de capture, pas de démontrer un exemple utile de (?:). (?:)est utile lorsque vous souhaitez regrouper une sous-expression (par exemple lorsque vous souhaitez appliquer des quantificateurs à une sous-expression non atomique ou si vous souhaitez restreindre la portée de a |), mais que vous ne voulez rien capturer.
sepp2k

26

MOTIVATION HISTORIQUE:

L'existence de groupes non capturants peut s'expliquer par l'utilisation de parenthèses.

Considérez les expressions (a|b)cet a|bc, en raison de la priorité de la concaténation |, ces expressions représentent deux langues différentes ( {ac, bc}et {a, bc}respectivement).

Cependant, les parenthèses sont également utilisées comme groupe d'appariement (comme expliqué par les autres réponses ...).

Lorsque vous souhaitez avoir des parenthèses mais ne pas capturer la sous-expression, vous utilisez des GROUPES NON CAPTURANTS. Dans l'exemple,(?:a|b)c


6
Je me demandais pourquoi. Comme je pense que le "pourquoi" est vital pour mémoriser cette information.
JMI MADISON

22

Permettez-moi d'essayer ceci avec un exemple:

Code regex: (?:animal)(?:=)(\w+)(,)\1\2

Chaîne de recherche:

Ligne 1 - animal=cat,dog,cat,tiger,dog

Ligne 2 - animal=cat,cat,dog,dog,tiger

Ligne 3 - animal=dog,dog,cat,cat,tiger

(?:animal) -> Groupe 1 non capturé

(?:=)-> Groupe 2 non capturé

(\w+)-> Groupe capturé 1

(,)-> Capturé Groupe 2

\1 -> résultat du groupe 1 capturé, c.-à-d. que la ligne 1 est le chat, la ligne 2 le chat, la ligne 3 le chien

\2 -> résultat du groupe 2 capturé, c'est-à-dire virgule (,)

Donc, dans ce code, en donnant \1et \2nous rappelons ou répétons le résultat des groupes capturés 1 et 2 respectivement plus tard dans le code.

Selon l'ordre du code (?:animal)doit être le groupe 1 et (?:=)doit être le groupe 2 et continue ..

mais en donnant le ?:nous faisons le match-group non capturé (qui ne compte pas dans le groupe apparié, donc le numéro de groupement commence à partir du premier groupe capturé et non le non capturé), de sorte que la répétition du résultat du match-group (?:animal)ne peut pas être appelé plus tard dans le code.

J'espère que cela explique l'utilisation du groupe non capturant.

entrez la description de l'image ici


14

Les groupes qui vous capturent peuvent être utilisés plus tard dans l'expression régulière pour correspondre OU vous pouvez les utiliser dans la partie de remplacement de l'expression régulière. Faire un groupe non capturant exempte simplement ce groupe d'être utilisé pour l'une ou l'autre de ces raisons.

Les groupes sans capture sont parfaits si vous essayez de capturer beaucoup de choses différentes et qu'il y a certains groupes que vous ne voulez pas capturer.

C'est à peu près la raison pour laquelle ils existent. Pendant que vous apprenez les groupes, découvrez les groupes atomiques , ils font beaucoup! Il existe également des groupes de contournement, mais ils sont un peu plus complexes et peu utilisés.

Exemple d'utilisation plus tard dans l'expression régulière (référence arrière):

<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1> [Recherche une balise xml (sans support ns)]

([A-Z][A-Z0-9]*) est un groupe de capture (dans ce cas, c'est la variable)

Plus tard dans l'expression régulière, \1cela signifie qu'il ne correspondra qu'au même texte que celui du premier groupe (le ([A-Z][A-Z0-9]*)groupe) (dans ce cas, il correspond à la balise de fin).


pourriez-vous donner un exemple simple de la façon dont il sera utilisé plus tard pour correspondre à OU?
never_had_a_name

je veux dire que vous pouvez utiliser pour faire correspondre plus tard ou vous pouvez l'utiliser dans le remplacement. Le ou dans cette phrase était juste pour vous montrer qu'il y a deux utilisations pour un groupe de capture
Bob Fincheimer

9

Eh bien, je suis un développeur JavaScript et j'essaierai d'expliquer son importance en ce qui concerne JavaScript.

Considérez un scénario dans lequel vous souhaitez faire correspondre cat is animal lorsque vous souhaitez faire correspondre chat et animal et que les deux devraient avoir un isentre eux.

 // this will ignore "is" as that's is what we want
"cat is animal".match(/(cat)(?: is )(animal)/) ;
result ["cat is animal", "cat", "animal"]

 // using lookahead pattern it will match only "cat" we can
 // use lookahead but the problem is we can not give anything
 // at the back of lookahead pattern
"cat is animal".match(/cat(?= is animal)/) ;
result ["cat"]

 //so I gave another grouping parenthesis for animal
 // in lookahead pattern to match animal as well
"cat is animal".match(/(cat)(?= is (animal))/) ;
result ["cat", "cat", "animal"]

 // we got extra cat in above example so removing another grouping
"cat is animal".match(/cat(?= is (animal))/) ;
result ["cat", "animal"]

7

Dans les expressions régulières complexes, vous pouvez avoir la situation où vous souhaitez utiliser un grand nombre de groupes dont certains sont là pour la correspondance de répétition et dont certains sont là pour fournir des références en arrière. Par défaut, le texte correspondant à chaque groupe est chargé dans le tableau de référence arrière. Lorsque nous avons beaucoup de groupes et que nous devons seulement pouvoir en référencer certains à partir du tableau de référence arrière, nous pouvons remplacer ce comportement par défaut pour indiquer à l'expression régulière que certains groupes ne sont là que pour la gestion des répétitions et n'ont pas besoin d'être capturés et stockés dans le tableau de référence arrière.


7

Je ne peux pas commenter les meilleures réponses pour dire ceci: je voudrais ajouter un point explicite qui n'est implicite que dans les meilleures réponses:

Le groupe non capturant (?...) ne supprime aucun caractère de la correspondance complète d'origine, il réorganise uniquement l'expression rationnelle visuellement au programmeur.

Pour accéder à une partie spécifique de l'expression régulière sans caractères étrangers définis, vous devez toujours utiliser .group(<index>)


2
Vous avez fourni le conseil le plus important qui manquait dans le reste des réponses. J'ai essayé tous les exemples qu'ils contiennent et j'ai utilisé le plus grand choix d'exemples, car je n'ai pas obtenu le résultat souhaité. Seul votre message m'a montré où je me suis trompé.
Seshadri R

Heureux de l'entendre!
Scott Anderson

6

Les groupes non capturants tl; dr , comme leur nom l'indique, sont les parties de l'expression régulière que vous ne souhaitez pas inclure dans la correspondance et ?:constituent un moyen de définir un groupe comme étant non capturant.

Disons que vous avez une adresse e-mail example@example.com. L'expression régulière suivante créera deux groupes , la partie id et la partie @ example.com. (\p{Alpha}*[a-z])(@example.com). Par souci de simplicité, nous extrayons le nom de domaine entier, y compris le @caractère.

Maintenant, disons, vous n'avez besoin que de la partie id de l'adresse. Ce que vous voulez faire, c'est saisir le premier groupe du résultat du match, entouré par ()l'expression régulière et la façon de le faire est d'utiliser la syntaxe de groupe non capturante, c'est-à-dire ?:. Ainsi, l'expression régulière (\p{Alpha}*[a-z])(?:@example.com)ne renverra que la partie id de l'e-mail.


5

Une chose intéressante que j'ai rencontrée est le fait que vous pouvez avoir un groupe de capture à l'intérieur d'un groupe non-capture. Jetez un œil à l'expression rationnelle ci-dessous pour faire correspondre les URL Web:

var parse_url_regex = /^(?:([A-Za-z]+):)(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;

Chaîne d'URL d'entrée:

var url = "http://www.ora.com:80/goodparts?q#fragment";

Le premier groupe de mon expression régulière (?:([A-Za-z]+):)est un groupe non capturant qui correspond au schéma de protocole et au :caractère deux-points , c'est-à-dire, http:mais lorsque j'exécutais sous le code, je voyais que le 1er index du tableau renvoyé contenait la chaîne httplorsque je pensais cela httpet deux points :les deux ne seront pas signalés car ils font partie d'un groupe non capturant.

console.debug(parse_url_regex.exec(url));

entrez la description de l'image ici

J'ai pensé que si le premier groupe (?:([A-Za-z]+):)est un groupe non capturant, alors pourquoi il renvoie une httpchaîne dans le tableau de sortie.

Donc, si vous remarquez qu'il y a un groupe imbriqué ([A-Za-z]+)dans le groupe non capturant. Ce groupe imbriqué ([A-Za-z]+)est un groupe de capture (n'ayant pas ?:au début) en lui-même à l'intérieur d'un groupe non-capture (?:([A-Za-z]+):). C'est pourquoi le texte est httptoujours capturé, mais le :caractère deux-points qui se trouve à l'intérieur du groupe de non-capture mais à l'extérieur du groupe de capture n'est pas signalé dans le tableau de sortie.


2

Ouvrez vos devTools Google Chrome, puis l'onglet Console: et saisissez ceci:

"Peace".match(/(\w)(\w)(\w)/)

Exécutez-le et vous verrez:

["Pea", "P", "e", "a", index: 0, input: "Peace", groups: undefined]

Le JavaScriptmoteur RegExp capture trois groupes, les éléments avec les index 1,2,3. Utilisez maintenant une marque non capturante pour voir le résultat.

"Peace".match(/(?:\w)(\w)(\w)/)

Le résultat est:

["Pea", "e", "a", index: 0, input: "Peace", groups: undefined]

C'est évident ce qui n'est pas le groupe de capture.


2

Je pense que je vous donnerais la réponse. N'utilisez pas de variables de capture sans vérifier que la correspondance a réussi.

Les variables de capture $1, etc. ne sont valides que si la correspondance a réussi et ne sont pas non plus effacées.

#!/usr/bin/perl  
use warnings;
use strict;   
$_ = "bronto saurus burger";
if (/(?:bronto)? saurus (steak|burger)/)
{
    print "Fred wants a  $1";
}
else
{
    print "Fred dont wants a $1 $2";
}

Dans l'exemple ci-dessus, pour éviter de capturer bronto $1, (?:)est utilisé.

Si le motif est apparié, il $1est capturé comme motif groupé suivant.

Ainsi, la sortie sera comme ci-dessous:

Fred wants a burger

Il est utile si vous ne souhaitez pas enregistrer les correspondances.


1

C'est extrêmement simple, nous pouvons comprendre avec un exemple de date simple, supposons que si la date est mentionnée comme 1er janvier 2019 ou 2 mai 2019 ou toute autre date et nous voulons simplement la convertir au format jj / mm / aaaa , nous n'aurions pas besoin du mois nom qui est janvier ou février d'ailleurs, afin de capturer la partie numérique, mais pas le suffixe (facultatif), vous pouvez utiliser un groupe non capturant.

de sorte que l'expression régulière serait,

([0-9]+)(?:January|February)?

C'est aussi simple que ça.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.