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));truetest 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]);}450,00 €87,35 €23,10 €- El patrón tiene la bandera
g: «todas las coincidencias». Sin ella,matchAllse detiene con unTypeError. statement.matchAll(amount)da las coincidencias una a una, de izquierda a derecha. Por eso está en unfor…of: cada vuelta recibe la siguiente, hasta que se acaban.mes una coincidencia encontrada: un array en el quem[0]es su texto.
Más precisamente, para curiosos
matchcon la banderagte da directamente un array con los textos de las coincidencias. Singda solo la primera coincidencia, con sus grupos (nullsi 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]);[ '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}`);ERROR de 0 a 5ERROR de 26 a 31Total: 2m.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);\d+ es «uno o más dígitos», así que «12» es una sola coincidencia. Con \d sin +, cada dígito iría por separado: [1][2][4][2]. El espacio y la coma cortan la secuencia, así que 12 y 4 no se juntan.
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]);}15.11.202615 / 11 / 20262026-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.matchsin la banderagsirve cuando solo necesitas la primera coincidencia. Si no hay ninguna,mesnullyif (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
- El patrón de la fecha tiene tres pares de paréntesis: tres grupos.
- match sin la bandera g busca la primera coincidencia. m es un array con ella y los grupos, o null.
- match encuentra la fecha: del índice 7 al 17.
- Cada grupo recuerda su parte: el grupo 1 es 15, el grupo 2 es 11, el grupo 3 es 2026.
- Las partes están en el array m, cada una bajo su índice. m[0] es toda la coincidencia.
- m[0] es toda la coincidencia: 15.11.2026.
- Los tres grupos, uno por uno.
- 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 - 1es 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 esundefined.
const m = "Propina 5 €".match(/(\d+)(,\d{2})? €/);console.log(m.length - 1);console.log(m[1], m[2]);25 undefinedLa idea
Grupos con nombre y reemplazo
(?<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} €`);}Chocolate caliente: 6.4 €Tostada: 2.9 €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>"));Plazo: 2026-11-15, recordatorio: 2026-11-08Plazo: 11/15/2026, recordatorio: 11/08/2026matchAll: 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"));Precio: $5- También puedes usar un grupo en el propio patrón:
\1significa «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]);}Una sola coincidencia, toda la línea. .+ toma todo lo que puede: llega al final del texto y luego devuelve carácter a carácter hasta encontrar un >. El primer > desde el final es el último de la línea. Los cuantificadores así se llaman codiciosos.
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, ""));<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.
g, así que match da solo la primera coincidencia. Con /#\d+/g, match da un array con los textos de todas.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]);}Sin conexión con la base de datosDisco 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.spermite que.atrape también un salto de línea.gya la conoces: sin ella,matchAllno 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"));Repetida: caféRepetida: favorfalse 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.
Este es contenido viejo. Más contenido viejo, no los viejos.Este es contenido nuevo. Más contenido nuevo, no los viejos.Reemplazos: 22. 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(...).
proprograma producto banana aprobar profesor improvisarprogramaproductoprofesor3. 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.
Mira el sitio https://www.example.com y este: http://www.test.com. Los documentos están en https://docs.example.com/guide/start.https://www.example.comhttp://www.test.comhttps://docs.example.com/guide/start4. Fechas a ISO. Convierte cada fecha dd.mm.aaaa del texto a la forma aaaa-mm-dd con un solo replace.
Contrato del 03.10.2026, pago hasta el 15.10.2026.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 banderau. - 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 yreplace.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Métodos de instancia · static: lo que comparten todos los objetos · Cadenas · Expresiones regulares
- Ejercicios sobre el tema: Texto y archivos