¿Qué es un grupo que no captura en expresiones regulares?

Respuestas:

2331

Déjame intentar explicar esto con un ejemplo.

Considere el siguiente texto:

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

Ahora, si aplico la expresión regular a continuación sobre ella ...

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

... obtendría el siguiente resultado:

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"

Pero no me importa el protocolo, solo quiero el host y la ruta de la URL. Entonces, cambio la expresión regular para incluir el grupo que no captura (?:).

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

Ahora, mi resultado se ve así:

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

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

¿Ver? El primer grupo no ha sido capturado. El analizador lo usa para hacer coincidir el texto, pero lo ignora más tarde, en el resultado final.


EDITAR:

Según lo solicitado, permítanme tratar de explicar los grupos también.

Bueno, los grupos tienen muchos propósitos. Pueden ayudarlo a extraer información exacta de una coincidencia más grande (que también se puede nombrar), le permiten volver a emparejar un grupo coincidente anterior y pueden usarse para sustituciones. Probemos algunos ejemplos, ¿de acuerdo?

Imagine que tiene algún tipo de XML o HTML (tenga en cuenta que la expresión regular puede no ser la mejor herramienta para el trabajo , pero es bueno como ejemplo). Desea analizar las etiquetas, por lo que podría hacer algo como esto (he agregado espacios para que sea más fácil de entender):

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

La primera expresión regular tiene un grupo con nombre (TAG), mientras que la segunda usa un grupo común. Ambas expresiones regulares hacen lo mismo: usan el valor del primer grupo (el nombre de la etiqueta) para que coincida con la etiqueta de cierre. La diferencia es que el primero usa el nombre para que coincida con el valor, y el segundo usa el índice de grupo (que comienza en 1).

Probemos algunas sustituciones ahora. Considere el siguiente texto:

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

Ahora, usemos esta expresión regular tonta sobre ella:

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

Esta expresión regular combina palabras con al menos 3 caracteres y usa grupos para separar las primeras tres letras. El resultado es este:

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"
...

Entonces, si aplicamos la cadena de sustitución:

$1_$3$2_$4

... sobre él, estamos tratando de usar el primer grupo, agregar un guión bajo, usar el tercer grupo, luego el segundo grupo, agregar otro guión bajo y luego el cuarto grupo. La cadena resultante sería como la de abajo.

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.

También puede usar grupos con nombre para las sustituciones, usando ${name}.

Para jugar con expresiones regulares, recomiendo http://regex101.com/ , que ofrece una buena cantidad de detalles sobre cómo funciona la expresión regular; También ofrece algunos motores regex para elegir.

Ricardo Nolde
fuente
3
@ajsie: los grupos tradicionales (de captura) son más útiles si está realizando una operación de reemplazo en los resultados. Aquí hay un ejemplo en el que tomo apellidos y nombres separados por comas y luego invierto su orden (gracias a grupos con nombre) ... regexhero.net/tester/?id=16892996-64d4-4f10-860a-24f28dad7e30
Steve Wortham
2
No, no es lo mismo.
Ricardo Nolde
44
También podría señalar que los grupos que no capturan son especialmente útiles cuando se usa la expresión regular como delimitadores divididos: "Alice and Bob" -split "\ s + (?: and | or) \ s +"
Yevgeniy
77
Sería interesante tener la diferencia entre los grupos que no capturan (? :), y las afirmaciones anticipadas y posteriores (? =,?!) Explicadas. Recién comencé a aprender sobre expresiones regulares, pero por lo que entiendo, los grupos que no capturan se usan para hacer coincidir y "devolver" lo que coinciden, pero ese "valor de retorno" no se "almacena" para hacer referencia a las referencias. Por otro lado, las afirmaciones de mirar hacia atrás y mirar hacia atrás no solo no están "almacenadas", sino que tampoco son parte de una coincidencia, simplemente afirman que algo coincidiría, pero su valor de "coincidencia" se ignora, si no me equivoco. . (¿Estoy más o menos en lo cierto?)
Christian
55
[] es un conjunto; [123] coincide con cualquier carácter dentro del conjunto una vez; [^ 123] coincide con cualquier cosa que NO esté dentro del conjunto una vez; [^ / \ r \ n] + coincide con uno o más caracteres que son diferentes de /, \ r, \ n.
Ricardo Nolde
180

Puede usar grupos de captura para organizar y analizar una expresión. Un grupo que no captura tiene el primer beneficio, pero no tiene la sobrecarga del segundo. Todavía puede decir que un grupo sin captura es opcional, por ejemplo.

Supongamos que desea hacer coincidir el texto numérico, pero algunos números podrían escribirse como 1º, 2º, 3º, 4º, ... Si desea capturar la parte numérica, pero no el sufijo (opcional), puede usar un grupo que no sea de captura .

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

Eso coincidirá con los números en la forma 1, 2, 3 ... o en la forma 1ra, 2da, 3ra, ... pero solo capturará la parte numérica.

Bill el lagarto
fuente
3
Conciso y probablemente la mejor explicación aquí.
NelsonGon
107

?: se usa cuando desea agrupar una expresión, pero no desea guardarla como una parte coincidente / capturada de la cadena.

Un ejemplo sería algo que coincida con una dirección IP:

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

Tenga en cuenta que no me importa guardar los primeros 3 octetos, pero la (?:...)agrupación me permite acortar la expresión regular sin incurrir en la sobrecarga de capturar y almacenar una coincidencia.

RC.
fuente
38

Hace que el grupo no se capture, lo que significa que la subcadena coincidente con ese grupo no se incluirá en la lista de capturas. Un ejemplo en rubí para ilustrar la diferencia:

"abc".match(/(.)(.)./).captures #=> ["a","b"]
"abc".match(/(?:.)(.)./).captures #=> ["b"]
sepp2k
fuente
¿Por qué no podemos simplemente usar "abc" .match (/.(.)./). Captura aquí?
PRASANNA SARAF
@PRASANNASARAF Puedes, por supuesto. El objetivo del código era mostrar que (?:)no produce una captura, no demostrar un ejemplo útil de (?:). (?:)es útil cuando desea agrupar una subexpresión (por ejemplo, cuando desea aplicar cuantificadores a una subexpresión no atómica o si desea restringir el alcance de a |), pero no desea capturar nada.
sepp2k
26

MOTIVACIÓN HISTÓRICA:

La existencia de grupos que no capturan puede explicarse con el uso de paréntesis.

Considere las expresiones (a|b)cy a|bc, debido a la prioridad de concatenación |, estas expresiones representan dos idiomas diferentes ( {ac, bc}y {a, bc}respectivamente).

Sin embargo, los paréntesis también se usan como un grupo coincidente (como se explica en las otras respuestas ...).

Cuando desea tener paréntesis pero no capturar la subexpresión, usa GRUPOS NO CAPTURANTES. En el ejemplo,(?:a|b)c

user2369060
fuente
66
Me preguntaba por qué. Como creo, el "por qué" es vital para memorizar esta información.
JMI MADISON
22

Déjame probar esto con un ejemplo:

Código de expresiones regulares: (?:animal)(?:=)(\w+)(,)\1\2

Cadena de búsqueda:

Línea 1 - animal=cat,dog,cat,tiger,dog

Línea 2 - animal=cat,cat,dog,dog,tiger

Línea 3 - animal=dog,dog,cat,cat,tiger

(?:animal) -> Grupo 1 no capturado

(?:=)-> Grupo 2 no capturado

(\w+)-> Grupo capturado 1

(,)-> Grupo capturado 2

\1 -> resultado del grupo 1 capturado, es decir, en la línea 1 es gato, en la línea 2 es gato, en la línea 3 es perro.

\2 -> resultado del grupo 2 capturado, es decir, coma (,)

Entonces, en este código al dar \1y \2recordamos o repetimos el resultado del grupo capturado 1 y 2 respectivamente más adelante en el código.

Según el orden del código (?:animal)debe ser el grupo 1 y (?:=)debe ser el grupo 2 y continúa.

pero al dar ?:que hacemos que el grupo de coincidencia no sea capturado (lo que no cuenta en el grupo coincidente, entonces el número de agrupación comienza desde el primer grupo capturado y no el no capturado), de modo que la repetición del resultado del grupo de coincidencia (?:animal)no se puede llamar más tarde en el código.

Espero que esto explique el uso del grupo no capturador.

ingrese la descripción de la imagen aquí

Shekhar Gehlot
fuente
14

Los grupos que capturan pueden usar más adelante en la expresión regular para que coincidan O usted puede usarlos en la parte de reemplazo de la expresión regular. Crear un grupo sin captura simplemente exime a ese grupo de ser utilizado por cualquiera de estos motivos.

Los grupos sin captura son excelentes si está intentando capturar muchas cosas diferentes y hay algunos grupos que no desea capturar.

Esa es más o menos la razón por la que existen. Mientras aprende sobre grupos, aprenda sobre Grupos Atómicos , ¡ellos hacen mucho! También hay grupos de búsqueda, pero son un poco más complejos y no se usan tanto.

Ejemplo de uso posterior en la expresión regular (referencia inversa):

<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1> [Encuentra una etiqueta xml (sin soporte ns)]

([A-Z][A-Z0-9]*) es un grupo de captura (en este caso es el nombre de la etiqueta)

Más adelante en la expresión regular es lo \1que significa que solo coincidirá con el mismo texto que estaba en el primer grupo (el ([A-Z][A-Z0-9]*)grupo) (en este caso, coincide con la etiqueta final).

Bob Fincheimer
fuente
¿podría dar un ejemplo simple de cómo se usará más tarde para hacer coincidir OR?
never_had_a_name
Me refiero a que puedes usar para que coincida más tarde o puedes usarlo en el reemplazo. La o en esa oración fue solo para mostrarle que hay dos usos para un grupo de captura
Bob Fincheimer
9

Bueno, soy un desarrollador de JavaScript y trataré de explicar su importancia con respecto a JavaScript.

Considere un escenario en el que desea hacer coincidir cat is animal cuando desea hacer coincidir gato y animal y ambos deberían tener un punto isintermedio entre ellos.

 // 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"]
Gaurav
fuente
7

En expresiones regulares complejas, es posible que surja una situación en la que desee utilizar una gran cantidad de grupos, algunos de los cuales están allí para la coincidencia de repeticiones y otros para proporcionar referencias. Por defecto, el texto que coincide con cada grupo se carga en la matriz de referencia. Cuando tenemos muchos grupos y solo necesitamos poder hacer referencia a algunos de ellos desde la matriz de referencia inversa, podemos anular este comportamiento predeterminado para decirle a la expresión regular que ciertos grupos están ahí solo para el manejo de repeticiones y no necesitan ser capturados y almacenados en la matriz de referencia inversa.

Jack Peng
fuente
7

No puedo comentar las respuestas principales para decir esto: me gustaría agregar un punto explícito que solo está implícito en las respuestas principales:

El grupo (?...) que no captura no elimina ningún carácter de la coincidencia completa original, solo reorganiza la expresión regular visualmente para el programador.

Para acceder a una parte específica de la expresión regular sin caracteres extraños definidos, siempre deberá usar .group(<index>)

Scott Anderson
fuente
2
Has proporcionado la pista más importante que faltaba en el resto de las respuestas. Probé todos los ejemplos en ellos y utilicé el más selecto de improperios, ya que no obtuve el resultado deseado. Solo tu publicación me mostró dónde me equivoqué.
Seshadri R
¡Alegra oírlo!
Scott Anderson
6

tl; dr grupos que no capturan, como su nombre indica son las partes de la expresión regular que no desea que se incluyan en la coincidencia y ?:es una forma de definir que un grupo no captura.

Digamos que tienes una dirección de correo electrónico [email protected]. La siguiente expresión regular creará dos grupos , la parte de identificación y la parte @ example.com. (\p{Alpha}*[a-z])(@example.com). Por simplicidad, estamos extrayendo todo el nombre de dominio, incluido el @carácter.

Ahora digamos que solo necesita la parte de identificación de la dirección. Lo que desea hacer es tomar el primer grupo del resultado de la coincidencia, rodeado por ()la expresión regular y la forma de hacerlo es usar la sintaxis de grupo no capturador, es decir ?:. Entonces, la expresión regular (\p{Alpha}*[a-z])(?:@example.com)devolverá solo la parte de identificación del correo electrónico.

6pack niño
fuente
5

Una cosa interesante que encontré es el hecho de que puedes tener un grupo de captura dentro de un grupo que no es de captura. Eche un vistazo a la expresión regular a continuación para encontrar las URL web correspondientes:

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

Cadena de URL de entrada:

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

El primer grupo en mi expresión regular (?:([A-Za-z]+):)es un grupo sin captura que coincide con el esquema de protocolo y el :carácter de dos puntos , es decir, http:pero cuando estaba ejecutando debajo del código, estaba viendo que el primer índice de la matriz devuelta contenía la cadena httpcuando estaba pensando eso httpy dos puntos :ambos no serán reportados ya que están dentro de un grupo que no captura.

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

ingrese la descripción de la imagen aquí

Pensé que si el primer grupo (?:([A-Za-z]+):)es un grupo que no captura, entonces por qué está devolviendo una httpcadena en la matriz de salida.

Entonces, si observa que hay un grupo anidado ([A-Za-z]+)dentro del grupo que no captura. Ese grupo anidado ([A-Za-z]+)es un grupo de captura (que no tiene ?:al principio) en sí mismo dentro de un grupo sin captura (?:([A-Za-z]+):). Es por eso que el texto httpaún se captura, pero el :carácter de dos puntos que está dentro del grupo sin captura pero fuera del grupo de captura no se informa en la matriz de salida.

RBT
fuente
2

Abra sus DevTools de Google Chrome y luego la pestaña Consola: y escriba esto:

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

Ejecútalo y verás:

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

El JavaScriptmotor RegExp captura tres grupos, los elementos con índices 1,2,3. Ahora use la marca de no captura para ver el resultado.

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

El resultado es:

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

Esto es obvio lo que es el grupo no capturador.

AmerllicA
fuente
2

Creo que te daría la respuesta. No use variables de captura sin verificar que la coincidencia se realizó correctamente.

Las variables de captura $1, etc., no son válidas a menos que la coincidencia haya tenido éxito, y tampoco se borran.

#!/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";
}

En el ejemplo anterior, para evitar capturar bronto $1, (?:)se utiliza.

Si el patrón coincide, $1se captura como el siguiente patrón agrupado.

Entonces, la salida será la siguiente:

Fred wants a burger

Es útil si no desea que se guarden las coincidencias.

Harini
fuente
1

Es extremadamente simple, podemos entenderlo con un ejemplo de fecha simple, supongamos que si la fecha se menciona como 1 de enero de 2019 o 2 de mayo de 2019 o cualquier otra fecha y simplemente queremos convertirla al formato dd / mm / aaaa , no necesitaríamos el mes nombre que es enero o febrero para el caso, por lo que para capturar la parte numérica, pero no el sufijo (opcional), puede usar un grupo que no sea de captura.

entonces la expresión regular sería,

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

Es tan simple como eso.

Ahmad Naved
fuente