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

Resuelto nickf asked hace 15 años • 23 respuestas

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 (!!!)
Expandir fragmento

¿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í una nueva: ¿Por qué una subcadena coincidente devuelve "indefinido" en JavaScript? .


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

nickf avatar Jan 11 '09 14:01 nickf
Aceptado

Actualización: 2019-09-10

La antigua forma de iterar sobre múltiples coincidencias no era muy intuitiva. Esto llevó a la propuesta del String.prototype.matchAllmétodo. Este nuevo método está en la especificación ECMAScript 2020 . Nos brinda una API limpia y resuelve múltiples problemas. Está en los principales navegadores y motores JS desde Chrome 73+/Node 12+ y Firefox 67+.

El método devuelve un iterador y se utiliza 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)
}
Expandir fragmento

Como devuelve un iterador, podemos decir que es diferido, esto es útil cuando se maneja un número particularmente grande de grupos de captura o cadenas muy grandes. Pero si lo necesita, el resultado se puede transformar fácilmente en un Array usando la sintaxis extendida 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, mientras esta propuesta obtiene un apoyo más amplio, puede utilizar el paquete de corrección oficial .

Además, el funcionamiento interno del método es sencillo. 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 realizar múltiples coincidencias.

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

También me alegra ver que incluso se hizo referencia a esta pregunta de StackOverflow en las discusiones de la propuesta .

respuesta original

Puedes acceder a grupos de captura como este:

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

Y si hay varias coincidencias, puedes iterar sobre ellas:

var myString = "something format_abc";
var myRegexp = new RegExp("(?:^|\\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);
}
Expandir fragmento

Christian C. Salvadó avatar Jan 11 '2009 07:01 Christian C. Salvadó

A continuación se muestra un método que puede utilizar para obtener el enésimo grupo de captura 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);
Expandir fragmento

Mathias Bynens avatar Jan 08 '2013 08:01 Mathias Bynens

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

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

PhiLho avatar Jan 11 '2009 09:01 PhiLho