Ir al contenido
wannacode

Expresiones regulares: grupos y reemplazo

En resumen: Los grupos de una expresión regular, partes entre paréntesis, extraen trozos de cada coincidencia; en JavaScript los encuentras con matchAll y los usas en un reemplazo con $1.

Problema

El extracto del banco

Tu banco te envía un texto con los pagos del mes. Quieres cada importe por separado para sumarlos:

const statement = "Alquiler 450,00 € el 01.10, luz 87,35 € el 05.10, agua 23,10 € el 12.10";
console.log(/\d+,\d{2} €/.test(statement));
Salida
true
test solo dice «sí, hay un importe en algún sitio». replace cambiaría los importes, pero no te los da. Necesitas una forma de encontrar cada coincidencia y tomarla.

La idea

matchAll

Para buscar, una cadena tiene el método matchAll:

const statement = "Alquiler 450,00 € el 01.10, luz 87,35 € el 05.10, agua 23,10 € el 12.10";
const amount = /\d+,\d{2} €/g;
for (const m of statement.matchAll(amount)) {
console.log(m[0]);
}
Salida
450,00 €
87,35 €
23,10 €
  • El patrón tiene la bandera g: «todas las coincidencias». Sin ella, matchAll se detiene con un TypeError.
  • statement.matchAll(amount) da las coincidencias una a una, de izquierda a derecha. Por eso está en un for…of: cada vuelta recibe la siguiente, hasta que se acaban.
  • m es una coincidencia encontrada: un array en el que m[0] es su texto.
Más precisamente, para curiosos
  • match con la bandera g te da directamente un array con los textos de las coincidencias. Sin g da solo la primera coincidencia, con sus grupos (null si no hay ninguna).
const statement = "Alquiler 450,00 € el 01.10, luz 87,35 € el 05.10, agua 23,10 € el 12.10";
console.log(statement.match(/\d+,\d{2} €/g));
console.log(statement.match(/\d+,\d{2} €/)[0]);
Salida
[ '450,00 €', '87,35 €', '23,10 €' ]
450,00 €

Míralo

Dónde está la coincidencia

Además del texto, la coincidencia sabe también dónde se encontró:

const log = "ERROR disco; INFO inicio; ERROR red";
let count = 0;
for (const m of log.matchAll(/ERROR/g)) {
count++;
console.log(`${m[0]} de ${m.index} a ${m.index + m[0].length}`);
}
console.log(`Total: ${count}`);
Salida
ERROR de 0 a 5
ERROR de 26 a 31
Total: 2
m.index es el índice del primer carácter de la coincidencia. El final es m.index + m[0].length: el índice después del último, como con slice. Por eso log.slice(m.index, m.index + m[0].length) es lo mismo que m[0].

Pruébalo

¿Un dígito o un número?

¿Qué imprimirá el programa?

let result = "";
for (const m of "Mesa 12, 4 personas, 2 botellas".matchAll(/\d+/g)) {
result += `[${m[0]}]`;
}
console.log(result);
Respuesta

La idea

Grupos: partes de la coincidencia

Los paréntesis en el patrón forman un grupo. La coincidencia recuerda por separado lo que coincidió con cada grupo:

const date = /(\d{2})\.(\d{2})\.(\d{4})/;
const m = "Vence: 15.11.2026".match(date);
if (m) {
console.log(m[0]);
console.log(m[1] + " / " + m[2] + " / " + m[3]);
console.log(m[3] + "-" + m[2] + "-" + m[1]);
}
Salida
15.11.2026
15 / 11 / 2026
2026-11-15
  • Los grupos se numeran por sus paréntesis de apertura, de izquierda a derecha, empezando por 1.
  • m[1] es el grupo 1. m[0] es toda la coincidencia.
  • match sin la bandera g sirve cuando solo necesitas la primera coincidencia. Si no hay ninguna, m es null y if (m) la salta.

Así tomas por separado el día, el mes y el año de una fecha, y los ordenas como necesites.

const date = /(\d{2})\.(\d{2})\.(\d{4})/;
const m = "Vence: 15.11.2026".match(date);
if (m) {
console.log(m[0]);
console.log(m[1] + " / " + m[2] + " / " + m[3]);
console.log(m[3] + "-" + m[2] + "-" + m[1]);
}
Paso a paso, en palabras
  1. El patrón de la fecha tiene tres pares de paréntesis: tres grupos.
  2. match sin la bandera g busca la primera coincidencia. m es un array con ella y los grupos, o null.
  3. match encuentra la fecha: del índice 7 al 17.
  4. Cada grupo recuerda su parte: el grupo 1 es 15, el grupo 2 es 11, el grupo 3 es 2026.
  5. Las partes están en el array m, cada una bajo su índice. m[0] es toda la coincidencia.
  6. m[0] es toda la coincidencia: 15.11.2026.
  7. Los tres grupos, uno por uno.
  8. En orden inverso, las partes dan 2026-11-15.
Más precisamente, para curiosos
  • (?:...) es un grupo que solo mantiene las partes juntas, sin recordarlas. Por ejemplo, (?:\+359|0)8[789]\d{7} no forma un grupo 1.
  • m.length - 1 es el número de grupos: el array tiene toda la coincidencia y un lugar para cada grupo.
  • Si un grupo no participó en la coincidencia (por ejemplo, (,\d{2})? no estaba), su lugar es undefined.
const m = "Propina 5 €".match(/(\d+)(,\d{2})? €/);
console.log(m.length - 1);
console.log(m[1], m[2]);
Salida
2
5 undefined

La idea

Grupos con nombre y reemplazo

Cuando hay muchos grupos, los números se confunden. Puedes ponerles nombre: (?<nombre>...), y luego m.groups.nombre. Aquí tienes una línea de la cuenta de una cafetería:
const item = /(?<qty>\d+) x (?<name>[A-Za-z ]+) a (?<price>\d+\.\d{2})/g;
for (const m of "2 x Chocolate caliente a 3.20; 1 x Tostada a 2.90".matchAll(item)) {
const qty = Number(m.groups.qty);
const price = Number(m.groups.price);
console.log(`${m.groups.name}: ${qty * price} €`);
}
Salida
Chocolate caliente: 6.4 €
Tostada: 2.9 €
Los grupos también funcionan en replace: en el texto de reemplazo, $1 es el grupo 1, y $<nombre> es el grupo con ese nombre. Aquí van las fechas, de la forma día.mes.año a ISO y a la forma estadounidense:
const text = "Plazo: 15.11.2026, recordatorio: 08.11.2026";
console.log(text.replace(/(\d{2})\.(\d{2})\.(\d{4})/g, "$3-$2-$1"));
console.log(text.replace(/(?<d>\d{2})\.(?<m>\d{2})\.(?<y>\d{4})/g, "$<m>/$<d>/$<y>"));
Salida
Plazo: 2026-11-15, recordatorio: 2026-11-08
Plazo: 11/15/2026, recordatorio: 11/08/2026
Aquí no necesitas matchAll: replace entiende los grupos por sí solo.
Más precisamente, para curiosos
  • $ en el texto de reemplazo es especial. Un signo de dólar de verdad es $$:
console.log("Precio: 5".replace(/(\d+)/, "$$$1"));
Salida
Precio: $5
  • También puedes usar un grupo en el propio patrón: \1 significa «lo mismo que el grupo 1». Así se atrapan palabras repetidas. Mira la tarjeta de las banderas.

Pruébalo

¿Cuánto toma +?

Quieres todas las etiquetas HTML de la línea:

¿Qué imprimirá el programa?

const html = "<b>Precio:</b> <i>12 €</i>";
for (const m of html.matchAll(/<.+>/g)) {
console.log(m[0]);
}
Respuesta

La idea

Codicioso y perezoso

Un ? después de un cuantificador lo hace perezoso: toma lo menos que puede y se detiene en la primera oportunidad.

const html = "<b>Precio:</b> <i>12 €</i>";
for (const m of html.matchAll(/<.+?>/g)) {
console.log(m[0]);
}
console.log(html.replace(/<[^>]+>/g, ""));
Salida
<b>
</b>
<i>
</i>
Precio: 12 €
  • <.+?> es «<, al menos un carácter y el primer > que venga después».
  • <[^>]+> hace lo mismo de otra manera: «<, caracteres que no son >, y >». No puede saltarse un >, así que además es más rápido. Con él quitas las etiquetas y solo queda el texto.
Codicioso Perezoso
* *?
+ +?
? ??
{2,5} {2,5}?

Pruébalo

Encuentra el error

El programa debería imprimir los tres números de pedido, pero imprime solo #1042. ¿En qué línea está el error?

Elige la línea con el error.

La idea

Banderas y líneas

Las banderas son letras después del patrón. De un archivo de registro con varias líneas tomas los mensajes de error, escritos en mayúsculas o en minúsculas:

const log = "INFO: Inicio\nERROR: Sin conexión con la base de datos\nINFO: Reintentando\nerror: Disco 95%";
const p = /^error: (.+)$/gim;
for (const m of log.matchAll(p)) {
console.log(m[1]);
}
Salida
Sin conexión con la base de datos
Disco 95%
  • i: mayúsculas y minúsculas son iguales.
  • m (multiline) hace que ^ y $ sean el inicio y el final de cada línea. Sin ella, son solo el inicio y el final de todo el texto, y el patrón no encontrará nada.
  • s permite que . atrape también un salto de línea.
  • g ya la conoces: sin ella, matchAll no funciona.
Más precisamente, para curiosos

Dos cosas que encontrarás en patrones ajenos:

  • Una referencia hacia atrás (backreference) \1: «el mismo texto que el grupo 1». Así se atrapan palabras repetidas.
  • Una búsqueda hacia delante (lookahead) (?=...): «de aquí en adelante hay en algún sitio ...», sin tomar caracteres. Varias seguidas son útiles para contraseñas: «tiene un dígito» y «tiene una mayúscula», y al menos 8 caracteres.
const twice = /(?<!\p{L})(\p{L}+) \1(?!\p{L})/gu;
for (const m of "Te espero en el café café, por favor favor sé puntual.".matchAll(twice)) {
console.log("Repetida: " + m[1]);
}
const strong = /^(?=.*\d)(?=.*[A-Z]).{8,}$/;
console.log(strong.test("secreto12"), strong.test("Secreto12"));
Salida
Repetida: café
Repetida: favor
false true

(?!...) es lo contrario: «de aquí en adelante no hay ...». También existe la búsqueda hacia atrás (lookbehind): (?<=...) y (?<!...). Con ellas se hace también el límite de palabra para letras como la «é» de arriba: \b en JavaScript solo conoce las letras latinas sin tilde.

Ejercicio

Encontrar, extraer, reemplazar

Resuelve las tareas en tu editor. El texto es una línea del teclado. En la lección «Archivos» las harás también con un archivo.

1. Buscar y reemplazar. Reemplaza la palabra «viejo» por «nuevo» en todos los sitios donde sea una palabra completa, y di cuántos reemplazos has hecho. Las partes de otras palabras («viejos») no se tocan.

Entrada
Este es contenido viejo. Más contenido viejo, no los viejos.
Salida
Este es contenido nuevo. Más contenido nuevo, no los viejos.
Reemplazos: 2

2. Palabras con prefijo. La primera línea es el prefijo, la segunda es el texto. Imprime cada palabra que empiece con el prefijo. El patrón se construye a partir de texto, así que es new RegExp(...).

Entrada
pro
programa producto banana aprobar profesor improvisar
Salida
programa
producto
profesor

3. Direcciones en un texto. Extrae todas las direcciones web que empiezan con http:// o https://. El punto al final de la frase no forma parte de la dirección.

Entrada
Mira el sitio https://www.example.com y este: http://www.test.com. Los documentos están en https://docs.example.com/guide/start.
Salida
https://www.example.com
http://www.test.com
https://docs.example.com/guide/start

4. Fechas a ISO. Convierte cada fecha dd.mm.aaaa del texto a la forma aaaa-mm-dd con un solo replace.

Entrada
Contrato del 03.10.2026, pago hasta el 15.10.2026.
Salida
Contrato del 2026-10-03, pago hasta el 2026-10-15.

Más tareas

  • Total del extracto. Toma los importes de la primera tarjeta con un grupo para los euros y otro para los céntimos, súmalos e imprime «Total: 560,45 €».
  • Hashtags. Extrae todas las palabras que empiezan con # de una publicación que mezcla letras latinas sin tilde y letras como «é» o «ñ». Piensa por qué necesitas \p{L} y la bandera u.
  • Sin etiquetas. Quita las etiquetas HTML de <p>El plazo es el <b>15.11</b>.</p> e imprime el texto limpio.
  • Teléfonos en un texto. Encuentra todos los números de móvil de un texto e imprímelos en la forma +359 88 812 3456, con grupos y replace.

Test

Ponte a prueba

1. ¿Qué da texto.matchAll(/…/g)?
2. El patrón es /(\d{2})\.(\d{2})\.(\d{4})/ y el texto es 15.11.2026. ¿Qué es m[2]?
3. ¿Qué da "05.03.2026".replace(/(\d+)\.(\d+)\.(\d+)/, "$2/$1")?
4. ¿Por qué "<.+>" atrapa todo el texto "<b>Precio</b>" y no solo "<b>"?
5. ¿Qué hace la bandera m?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·