¿Cómo se accede a los grupos coincidentes en una expresión regular de JavaScript?

1369

Quiero hacer coincidir una parte de una cadena usando una expresión regular y luego acceder a esa subcadena entre paréntesis:

var myString = "something format_abc"; // I want "abc"

var arr = /(?:^|\s)format_(.*?)(?:\s|$)/.exec(myString);

console.log(arr);     // Prints: [" format_abc", "abc"] .. so far so good.
console.log(arr[1]);  // Prints: undefined  (???)
console.log(arr[0]);  // Prints: format_undefined (!!!)

¿Qué estoy haciendo mal?


Descubrí que no había nada malo con el código de expresión regular anterior: la cadena real con la que estaba probando era esta:

"date format_%A"

Informar que "% A" no está definido parece un comportamiento muy extraño, pero no está directamente relacionado con esta pregunta, así que abrí uno nuevo, ¿Por qué una subcadena coincidente devuelve "indefinido" en JavaScript? .


El problema fue que console.logtoma sus parámetros como una printfdeclaración, y dado que la cadena que estaba registrando ( "%A") tenía un valor especial, estaba tratando de encontrar el valor del siguiente parámetro.

nickf
fuente

Respuestas:

1676

Puede acceder a grupos de captura como este:

var myString = "something format_abc";
var myRegexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
var match = myRegexp.exec(myString);
console.log(match[1]); // abc

Y si hay varias coincidencias, puede iterar sobre ellas:

var myString = "something format_abc";
var myRegexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
match = myRegexp.exec(myString);
while (match != null) {
  // matched text: match[0]
  // match start: match.index
  // capturing group n: match[n]
  console.log(match[0])
  match = myRegexp.exec(myString);
}

Editar: 2019-09-10

Como puede ver, la forma de iterar sobre múltiples coincidencias no era muy intuitiva. Esto condujo a la propuesta del String.prototype.matchAllmétodo. Se espera que este nuevo método se envíe en la especificación ECMAScript 2020 . Nos da una API limpia y resuelve múltiples problemas. Se ha comenzado a aterrizar en los principales navegadores y motores JS como Chrome 73+ / Node 12+ y Firefox 67+.

El método devuelve un iterador y se usa de la siguiente manera:

const string = "something format_abc";
const regexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
const matches = string.matchAll(regexp);
    
for (const match of matches) {
  console.log(match);
  console.log(match.index)
}

Como devuelve un iterador, podemos decir que es vago, esto es útil cuando se manejan números particularmente grandes de grupos de captura, o cadenas muy grandes. Pero si lo necesita, el resultado se puede transformar fácilmente en una matriz utilizando la sintaxis de propagación o el Array.frommétodo:

function getFirstGroup(regexp, str) {
  const array = [...str.matchAll(regexp)];
  return array.map(m => m[1]);
}

// or:
function getFirstGroup(regexp, str) {
  return Array.from(str.matchAll(regexp), m => m[1]);
}

Mientras tanto, aunque esta propuesta recibe un soporte más amplio, puede usar el paquete oficial de shim .

Además, el funcionamiento interno del método es simple. Una implementación equivalente usando una función generadora sería la siguiente:

function* matchAll(str, regexp) {
  const flags = regexp.global ? regexp.flags : regexp.flags + "g";
  const re = new RegExp(regexp, flags);
  let match;
  while (match = re.exec(str)) {
    yield match;
  }
}

Se crea una copia de la expresión regular original; Esto es para evitar efectos secundarios debido a la mutación de la lastIndexpropiedad al pasar por las coincidencias múltiples.

Además, debemos asegurarnos de que la expresión regular tenga la bandera global para evitar un bucle infinito.

También me alegra ver que incluso esta pregunta de StackOverflow fue mencionada en los debates de la propuesta .

CMS
fuente
114
+1 Tenga en cuenta que en el segundo ejemplo debe usar el objeto RegExp (no solo "/ myregexp /"), ya que mantiene el valor lastIndex en el objeto. Sin usar el objeto Regexp
iterará
77
@ianaz: ¿No creo que sea verdad? http://jsfiddle.net/weEg9/ parece funcionar en Chrome, al menos.
spinningarrow
16
¿Por qué hacer lo anterior en lugar de var match = myString.match(myRegexp); // alert(match[1]):?
JohnAllen
29
No es necesario un "nuevo RegExp" explícito, sin embargo, el bucle infinito ocurrirá a menos que se especifique / g
George C
44
Otra forma de no encontrarse con un bucle infinito es actualizar explícitamente la cadena, por ejemplostring = string.substring(match.index + match[0].length)
Olga
186

He aquí un método que se puede utilizar para obtener el n º captura de grupo para cada partido:

function getMatches(string, regex, index) {
  index || (index = 1); // default to the first capturing group
  var matches = [];
  var match;
  while (match = regex.exec(string)) {
    matches.push(match[index]);
  }
  return matches;
}


// Example :
var myString = 'something format_abc something format_def something format_ghi';
var myRegEx = /(?:^|\s)format_(.*?)(?:\s|$)/g;

// Get an array containing the first capturing group for every match
var matches = getMatches(myString, myRegEx, 1);

// Log results
document.write(matches.length + ' matches found: ' + JSON.stringify(matches))
console.log(matches);

Mathias Bynens
fuente
12
Esta es una respuesta muy superior a las demás porque muestra correctamente la iteración en todas las coincidencias en lugar de solo obtener una.
Rob Evans
13
Mnn tiene razón. Esto producirá un bucle infinito si la bandera 'g' no está presente. Ten mucho cuidado con esta función.
Druska
44
Mejoré esto para hacerlo similar al re.findall () de python. Agrupa todas las coincidencias en una matriz de matrices. También corrige el problema del bucle infinito modificador global. jsfiddle.net/ravishi/MbwpV
ravishi
55
@MichaelMikowski ahora acabas de ocultar tu ciclo infinito, pero tu código se ejecutará lentamente. Yo diría que es mejor que el código se rompa de manera incorrecta para que lo detecte en el desarrollo. Poner algunas iteraciones de bs máximo es descuidado. Ocultar los problemas en lugar de solucionar su causa raíz no es la respuesta.
wallacer
44
@MichaelMikowski que no es significativamente más lento cuando no estás alcanzando el límite de ejecución. Cuando lo estás, claramente es mucho más lento. No digo que su código no funcione, digo que en la práctica creo que causará más daño que bien. Las personas que trabajan en un entorno de desarrollo verán que el código funciona bien sin carga a pesar de realizar 10,000 ejecuciones innecesarias de algún fragmento de código. Luego lo llevarán a un entorno de producción y se preguntarán por qué su aplicación deja de funcionar. En mi experiencia, es mejor si las cosas se rompen de una manera obvia, y antes en el ciclo de desarrollo.
wallacer
58

var myString = "something format_abc";
var arr = myString.match(/\bformat_(.*?)\b/);
console.log(arr[0] + " " + arr[1]);

El \bno es exactamente lo mismo. (Funciona --format_foo/, pero no funciona format_a_b) Pero quería mostrar una alternativa a su expresión, que está bien. Por supuesto, la matchllamada es lo importante.

PhiLho
fuente
2
Es exactamente al revés. '\ b' delimita las palabras. palabra = '\ w' = [a-zA-Z0-9_]. "format_a_b" es una palabra.
BF
1
@BFHonestamente, agregué "no funciona format_a_b" como un pensamiento posterior hace 6 años, y no recuerdo lo que quise decir allí ... :-) Supongo que significa "no funciona para capturar asolo", es decir. La primera parte alfabética después format_.
PhiLho
1
Quería decir que \ b (- format_foo /} \ b no devuelve "--format_foo /" porque "-" y "/" no son caracteres de \ word. Pero \ b (format_a_b) \ b sí devuelve "format_a_b ". ¿Correcto? Me refiero a su declaración de texto entre paréntesis. (¡Sin voto negativo!)
BF
31

Con respecto a los ejemplos de paréntesis de coincidencias múltiples anteriores, estaba buscando una respuesta aquí después de no obtener lo que quería de:

var matches = mystring.match(/(?:neededToMatchButNotWantedInResult)(matchWanted)/igm);

Después de mirar las llamadas de función ligeramente complicadas con while y .push () arriba, me di cuenta de que el problema se puede resolver de manera muy elegante con mystring.replace () en su lugar (el reemplazo NO es el punto, y ni siquiera se hace , la opción de llamada a la función recursiva incorporada LIMPIO para el segundo parámetro es!):

var yourstring = 'something format_abc something format_def something format_ghi';

var matches = [];
yourstring.replace(/format_([^\s]+)/igm, function(m, p1){ matches.push(p1); } );

Después de esto, no creo que vuelva a usar .match () para casi nada nunca más.

Alexz
fuente
26

Por último, pero no menos importante, encontré una línea de código que funcionó bien para mí (JS ES6):

let reg = /#([\S]+)/igm; // Get hashtags.
let string = 'mi alegría es total! ✌🙌\n#fiestasdefindeaño #PadreHijo #buenosmomentos #france #paris';

let matches = (string.match(reg) || []).map(e => e.replace(reg, '$1'));
console.log(matches);

Esto devolverá:

['fiestasdefindeaño', 'PadreHijo', 'buenosmomentos', 'france', 'paris']
Sebastien H.
fuente
1
¡AUGE! Esa es la solución más elegante aquí. Encontré esto mejor que el replaceenfoque completo de Alexz porque este es menos anticipado y más elegante para obtener múltiples resultados. Buen trabajo en esto, Sebastien H.
Cody
Esto funciona tan bien que definitivamente va a entrar en mis utilidades :)
Cody
1
@Cody jaja gracias hombre!
Sebastien H.
19

Terminología utilizada en esta respuesta:

  • Partido indica el resultado de ejecutar el patrón de expresión en contra de su cadena de este modo: someString.match(regexPattern).
  • Los patrones coincidentes indican todas las partes coincidentes de la cadena de entrada, que residen todas dentro de la matriz de coincidencias . Estas son todas las instancias de su patrón dentro de la cadena de entrada.
  • Los grupos coincidentes indican todos los grupos a capturar, definidos en el patrón RegEx. (Los patrones dentro de paréntesis, así: /format_(.*?)/gdónde (.*?)sería un grupo coincidente). Estos residen dentro de patrones coincidentes .

Descripción

Para obtener acceso a los grupos coincidentes , en cada uno de los patrones coincidentes , necesita una función o algo similar para iterar sobre la coincidencia . Hay varias formas de hacerlo, como muestran muchas de las otras respuestas. La mayoría de las otras respuestas usan un ciclo while para iterar sobre todos los patrones coincidentes , pero creo que todos conocemos los peligros potenciales con ese enfoque. Es necesario hacer coincidir un new RegExp()patrón en lugar de solo el patrón en sí mismo, que solo se mencionó en un comentario. Esto se debe a que el .exec()método se comporta de manera similar a una función generadora : se detiene cada vez que hay una coincidencia , pero .lastIndexcontinúa desde allí en la próxima .exec()llamada.

Ejemplos de código

A continuación se muestra un ejemplo de una función searchStringque devuelve uno Arrayde todos los patrones coincidentes , donde cada uno matches un Arraycon todos los grupos coincidentes que contienen . En lugar de usar un bucle while, he proporcionado ejemplos usando tanto la Array.prototype.map()función como una forma más foreficiente : usando un bucle simple .

Versiones concisas (menos código, más azúcar sintáctico)

Estos son menos forEachefectivos ya que básicamente implementan un bucle en lugar del forbucle más rápido.

// Concise ES6/ES2015 syntax
const searchString = 
    (string, pattern) => 
        string
        .match(new RegExp(pattern.source, pattern.flags))
        .map(match => 
            new RegExp(pattern.source, pattern.flags)
            .exec(match));

// Or if you will, with ES5 syntax
function searchString(string, pattern) {
    return string
        .match(new RegExp(pattern.source, pattern.flags))
        .map(match =>
            new RegExp(pattern.source, pattern.flags)
            .exec(match));
}

let string = "something format_abc",
    pattern = /(?:^|\s)format_(.*?)(?:\s|$)/;

let result = searchString(string, pattern);
// [[" format_abc", "abc"], null]
// The trailing `null` disappears if you add the `global` flag

Versiones de rendimiento (más código, menos azúcar sintáctico)

// Performant ES6/ES2015 syntax
const searchString = (string, pattern) => {
    let result = [];

    const matches = string.match(new RegExp(pattern.source, pattern.flags));

    for (let i = 0; i < matches.length; i++) {
        result.push(new RegExp(pattern.source, pattern.flags).exec(matches[i]));
    }

    return result;
};

// Same thing, but with ES5 syntax
function searchString(string, pattern) {
    var result = [];

    var matches = string.match(new RegExp(pattern.source, pattern.flags));

    for (var i = 0; i < matches.length; i++) {
        result.push(new RegExp(pattern.source, pattern.flags).exec(matches[i]));
    }

    return result;
}

let string = "something format_abc",
    pattern = /(?:^|\s)format_(.*?)(?:\s|$)/;

let result = searchString(string, pattern);
// [[" format_abc", "abc"], null]
// The trailing `null` disappears if you add the `global` flag

Todavía tengo que comparar estas alternativas con las mencionadas anteriormente en las otras respuestas, pero dudo que este enfoque sea menos eficaz y menos a prueba de fallas que los otros.

Daniel Hallgren
fuente
19

String#matchAll(vea el borrador de la Etapa 3 / propuesta del 7 de diciembre de 2018 ), simplifica el acceso a todos los grupos en el objeto de coincidencia (tenga en cuenta que el Grupo 0 es la coincidencia completa, mientras que otros grupos corresponden a los grupos de captura en el patrón):

Con matchAlldisponible, puede evitar el whilebucle y execcon /g... En su lugar, al usarlo matchAll, obtiene un iterador que puede usar con el más conveniente for...of, la distribución de matriz o las Array.from()construcciones

Este método produce una salida similar a Regex.Matchesen C #, re.finditeren Python, preg_match_allen PHP.

Vea una demostración de JS (probada en Google Chrome 73.0.3683.67 (versión oficial), beta (64 bits)):

var myString = "key1:value1, key2-value2!!@key3=value3";
var matches = myString.matchAll(/(\w+)[:=-](\w+)/g);
console.log([...matches]); // All match with capturing group values

Los console.log([...matches])shows

ingrese la descripción de la imagen aquí

También puede obtener un valor de coincidencia o valores de grupo específicos utilizando

let matchData = "key1:value1, key2-value2!!@key3=value3".matchAll(/(\w+)[:=-](\w+)/g)
var matches = [...matchData]; // Note matchAll result is not re-iterable

console.log(Array.from(matches, m => m[0])); // All match (Group 0) values
// => [ "key1:value1", "key2-value2", "key3=value3" ]
console.log(Array.from(matches, m => m[1])); // All match (Group 1) values
// => [ "key1", "key2", "key3" ]

NOTA : Consulte los detalles de compatibilidad del navegador .

Wiktor Stribiżew
fuente
Ejemplo perfecto para pares de valores clave. Conciso y fácil de leer, muy simple de usar. Además, un mejor manejo de errores, la propagación devolverá una matriz vacía en lugar de nula, por lo que no más 'error, no hay propiedad' longitud 'de nulo'
Jarrod McGuire
17

Su sintaxis probablemente no sea la mejor para mantener. FF / Gecko define RegExp como una extensión de Function.
(FF2 fue tan lejos como typeof(/pattern/) == 'function')

Parece que esto es específico para FF: IE, Opera y Chrome arrojan excepciones para ello.

En su lugar, utilice cualquiera de los métodos mencionados anteriormente por otros: RegExp#execo String#match.
Ofrecen los mismos resultados:

var regex = /(?:^|\s)format_(.*?)(?:\s|$)/;
var input = "something format_abc";

regex(input);        //=> [" format_abc", "abc"]
regex.exec(input);   //=> [" format_abc", "abc"]
input.match(regex);  //=> [" format_abc", "abc"]
Jonathan Lonowski
fuente
16

¡No hay necesidad de invocar el execmétodo! Puede usar el método de "coincidencia" directamente en la cadena. Simplemente no olvides los paréntesis.

var str = "This is cool";
var matches = str.match(/(This is)( cool)$/);
console.log( JSON.stringify(matches) ); // will print ["This is cool","This is"," cool"] or something like that...

La posición 0 tiene una cadena con todos los resultados. La posición 1 tiene la primera coincidencia representada entre paréntesis, y la posición 2 tiene la segunda coincidencia aislada entre paréntesis. Los paréntesis anidados son complicados, ¡así que ten cuidado!

Andre Carneiro
fuente
44
Sin la bandera global, esto devuelve todas las coincidencias, con ella, solo obtendrás una grande, así que ten cuidado con eso.
Shadymilkman01
8

Un trazador de líneas que es práctico solo si tiene un par de paréntesis:

while ( ( match = myRegex.exec( myStr ) ) && matches.push( match[1] ) ) {};
Nabil Kadimi
fuente
44
Por qué nowhile (match = myRegex.exec(myStr)) matches.push(match[1])
willlma
7

Usando su código:

console.log(arr[1]);  // prints: abc
console.log(arr[0]);  // prints:  format_abc

Editar: Safari 3, si es importante.

sin párpados
fuente
7

Con es2018 ahora puede String.match()con grupos con nombre, hace que su expresión regular sea más explícita de lo que estaba tratando de hacer.

const url =
  '/programming/432493/how-do-you-access-the-matched-groups-in-a-javascript-regular-expression?some=parameter';
const regex = /(?<protocol>https?):\/\/(?<hostname>[\w-\.]*)\/(?<pathname>[\w-\./]+)\??(?<querystring>.*?)?$/;
const { groups: segments } = url.match(regex);
console.log(segments);

y obtendrás algo como

{protocolo: "https", nombre de host: "stackoverflow.com", nombre de ruta: "preguntas / 432493 / how-do-you-access-the-match-groups-in-a-javascript-regular-expression", querystring: " algunos = parámetro "}

David Cheung
fuente
6

function getMatches(string, regex, index) {
  index || (index = 1); // default to the first capturing group
  var matches = [];
  var match;
  while (match = regex.exec(string)) {
    matches.push(match[index]);
  }
  return matches;
}


// Example :
var myString = 'Rs.200 is Debited to A/c ...2031 on 02-12-14 20:05:49 (Clear Bal Rs.66248.77) AT ATM. TollFree 1800223344 18001024455 (6am-10pm)';
var myRegEx = /clear bal.+?(\d+\.?\d{2})/gi;

// Get an array containing the first capturing group for every match
var matches = getMatches(myString, myRegEx, 1);

// Log results
document.write(matches.length + ' matches found: ' + JSON.stringify(matches))
console.log(matches);

function getMatches(string, regex, index) {
  index || (index = 1); // default to the first capturing group
  var matches = [];
  var match;
  while (match = regex.exec(string)) {
    matches.push(match[index]);
  }
  return matches;
}


// Example :
var myString = 'something format_abc something format_def something format_ghi';
var myRegEx = /(?:^|\s)format_(.*?)(?:\s|$)/g;

// Get an array containing the first capturing group for every match
var matches = getMatches(myString, myRegEx, 1);

// Log results
document.write(matches.length + ' matches found: ' + JSON.stringify(matches))
console.log(matches);

Jack
fuente
3

Su código funciona para mí (FF3 en Mac) incluso si estoy de acuerdo con PhiLo en que la expresión regular probablemente debería ser:

/\bformat_(.*?)\b/

(Pero, por supuesto, no estoy seguro porque no conozco el contexto de la expresión regular).

PEZ
fuente
1
es una lista separada por espacios, así que pensé que estaría bien. extraño que ese código no funcionara para mí (FF3 Vista)
nickf
1
Sí, realmente extraño ¿Lo has probado solo en la consola Firebug? De una página vacía, quiero decir.
PEZ
2
/*Regex function for extracting object from "window.location.search" string.
 */

var search = "?a=3&b=4&c=7"; // Example search string

var getSearchObj = function (searchString) {

    var match, key, value, obj = {};
    var pattern = /(\w+)=(\w+)/g;
    var search = searchString.substr(1); // Remove '?'

    while (match = pattern.exec(search)) {
        obj[match[0].split('=')[0]] = match[0].split('=')[1];
    }

    return obj;

};

console.log(getSearchObj(search));
Pawel Kwiecien
fuente
2

Realmente no necesita un ciclo explícito para analizar múltiples coincidencias: pase una función de reemplazo como segundo argumento como se describe en String.prototype.replace(regex, func):

var str = "Our chief weapon is {1}, {0} and {2}!"; 
var params= ['surprise', 'fear', 'ruthless efficiency'];
var patt = /{([^}]+)}/g;

str=str.replace(patt, function(m0, m1, position){return params[parseInt(m1)];});

document.write(str);

El m0argumento representa la subcadena coincidente completa {0}, {1}, etc. m1representa el primer grupo de coincidencia, es decir, la parte encerrada entre paréntesis en la expresión regular que es 0para el primer partido. Y positiones el índice inicial dentro de la cadena donde se encontró el grupo coincidente, no utilizado en este caso.

ccpizza
fuente
1

Podemos acceder al grupo coincidente en expresiones regulares mediante el uso de una barra diagonal inversa seguido del número del grupo coincidente:

/([a-z])\1/

En el código \ 1 representado coincide con el primer grupo ([az])

Md. A. Barik
fuente
1

Solución de una línea:

const matches = (text,regex) => [...text.matchAll(regex)].map(([match])=>match)

Entonces puede usar de esta manera (debe usar / g):

matches("something format_abc", /(?:^|\s)format_(.*?)(?:\s|$)/g)

resultado:

[" format_abc"]
Caio Santos
fuente
0

Obtener todas las ocurrencias grupales

let m=[], s = "something format_abc  format_def  format_ghi";

s.replace(/(?:^|\s)format_(.*?)(?:\s|$)/g, (x,y)=> m.push(y));

console.log(m);

Kamil Kiełczewski
fuente
0

Si eres como yo y deseo que regex devuelva un Objeto como este:

{
    match: '...',
    matchAtIndex: 0,
    capturedGroups: [ '...', '...' ]
}

luego corta la función desde abajo

/**
 * @param {string | number} input
 *          The input string to match
 * @param {regex | string}  expression
 *          Regular expression 
 * @param {string} flags
 *          Optional Flags
 * 
 * @returns {array}
 * [{
    match: '...',
    matchAtIndex: 0,
    capturedGroups: [ '...', '...' ]
  }]     
 */
function regexMatch(input, expression, flags = "g") {
  let regex = expression instanceof RegExp ? expression : new RegExp(expression, flags)
  let matches = input.matchAll(regex)
  matches = [...matches]
  return matches.map(item => {
    return {
      match: item[0],
      matchAtIndex: item.index,
      capturedGroups: item.length > 1 ? item.slice(1) : undefined
    }
  })
}

let input = "key1:value1, key2:value2 "
let regex = /(\w+):(\w+)/g

let matches = regexMatch(input, regex)

console.log(matches)

Delcon
fuente
0

SOLO UTILICE RegExp. $ 1 ... $ n grupo, por ejemplo:

1.Para igualar el 1er grupo RegExp. $ 1

  1. Para que coincida con el segundo grupo RegExp. $ 2

si usa 3 grupos en regex likey (tenga en cuenta el uso después de string.match (regex))

RegExp. $ 1 RegExp. $ 2 RegExp. $ 3

 var str = "The rain in ${india} stays safe"; 
  var res = str.match(/\${(.*?)\}/ig);
  //i used only one group in above example so RegExp.$1
console.log(RegExp.$1)

//easiest way is use RegExp.$1 1st group in regex and 2nd grounp like
 //RegExp.$2 if exist use after match

var regex=/\${(.*?)\}/ig;
var str = "The rain in ${SPAIN} stays ${mainly} in the plain"; 
  var res = str.match(regex);
for (const match of res) {
  var res = match.match(regex);
  console.log(match);
  console.log(RegExp.$1)
 
}

ßãlãjî
fuente