¿Cómo se accede a los grupos coincidentes en una expresión regular de JavaScript?
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í una nueva: ¿Por qué una subcadena coincidente devuelve "indefinido" en JavaScript? .
El problema era que console.log
toma sus parámetros como una printf
declaración, y como la cadena que estaba registrando ( "%A"
) tenía un valor especial, intentaba encontrar el valor del siguiente parámetro.
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.matchAll
mé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)
}
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.from
mé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 lastIndex
propiedad 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
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);
}
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);
var myString = "something format_abc";
var arr = myString.match(/\bformat_(.*?)\b/);
console.log(arr[0] + " " + arr[1]);
No \b
es 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 match
llamada es lo importante.