ToolZen ToolZen
Developer checker client

Regex Tester

Test regular expressions with real-time match highlighting, capture group display, replace output, and multi-flag support.

Advertisement (Top Banner)
/ /

Flags: g = global, i = case-insensitive, m = multiline, s = dotAll, u = unicode

Use $1, $2… for capture groups

Advertisement (Bottom)

Una expresión regular es un programa pequeño y, como cualquier programa, es más fácil de arreglar cuando puedes verlo funcionar. Escribe un patrón, pega algo de texto y las coincidencias se encienden sobre la marcha. Aquí funciona el motor de expresiones regulares de JavaScript, y eso importa: los dialectos de regex difieren más de lo que la gente espera.

Algunos patrones pueden colgarse durante horas, por eso este corre en un worker

El motor de regex de JavaScript hace retroceso, y el retroceso puede explotar. El ejemplo de manual es (a+)+b: contra una cadena de treinta "a" sin ninguna "b", el motor prueba todas las formas de repartir esos treinta caracteres entre el cuantificador interior y el exterior —más de mil millones de caminos— antes de concluir que no hay coincidencia. Añade cinco caracteres más y tarda treinta veces más.

No es teórico. Ejecuta ese patrón en un probador ingenuo y la pestaña del navegador se bloquea por completo, porque una regex en ejecución no puede interrumpirse desde dentro de JavaScript. Por eso esta herramienta evalúa tu patrón en un Web Worker, en un hilo aparte, con un presupuesto de un segundo. Si el patrón lo revienta, el worker se mata y se te dice que el patrón es patológico, en vez de dejarte ver morir la página. La pestaña sigue respondiendo todo el rato.

La lección va mucho más allá de esta página: si patrones suministrados por usuarios llegan alguna vez a un motor de regex en tu servidor, ese motor se puede colgar igual. Tiene nombre —ReDoS— y ha tumbado servicios reales.

La bandera g hace que tu regex recuerde cosas

Este es el fallo de regex de JavaScript que más horas de depuración cuesta, y parece magia cuando te toca. Un objeto regex con la bandera g mantiene una propiedad lastIndex, y tanto .test() como .exec() la hacen avanzar. Así que llamar dos veces al mismo test sobre la misma cadena da respuestas distintas.

Pruébalo en una consola: const re = /a/g; re.test("a") devuelve true, luego re.test("a") devuelve false, luego true otra vez. Alterna eternamente. La primera llamada coincide en la posición 0 y pone lastIndex a 1; la segunda empieza a buscar desde la posición 1, no encuentra nada y reinicia lastIndex a 0. Nada está roto: la regex se comporta exactamente según la especificación, y es la especificación la que sorprende.

Las reglas prácticas: nunca guardes una regex con bandera g en una constante a nivel de módulo para reutilizarla con .test(). Si solo quieres un sí/no, quita la g. Si la necesitas, construye la regex de nuevo en cada uso, o reinicia tú mismo lastIndex. Esta herramienta esquiva todo el asunto construyendo una regex nueva con cada pulsación.

JavaScript no es PCRE

Un patrón sacado de una respuesta de Perl, PHP o Python a menudo funcionará aquí, y de vez en cuando no, de formas fáciles de pasar por alto. JavaScript no tiene grupos atómicos ni cuantificadores posesivos: justo las dos funciones que otros dialectos ofrecen para impedir la explosión de retroceso descrita arriba. No tiene recursión, así que los clásicos patrones para "encontrar paréntesis equilibrados" son sencillamente imposibles.

El lookbehind sí existe en JavaScript, desde ES2018, pero llegó tarde: Safari solo lo incorporó en la versión 16.4, en marzo de 2023. Si das soporte a dispositivos iOS antiguos, un patrón que use (?<=...) no es que no vaya a coincidir: lanzará un SyntaxError al construir la regex, llevándose por delante tu script. Es un fallo mucho más ruidoso que una coincidencia ausente, y conviene saberlo antes de poner uno en producción.

Las banderas, en breve

g encuentra todas las coincidencias en lugar de parar en la primera. i ignora mayúsculas y minúsculas. m cambia el significado de ^ y $: coinciden en los límites de línea en vez de solo al principio y al final de toda la cadena, que es lo que se suele querer al probar contra texto multilínea pegado. s hace que el punto también coincida con saltos de línea; sin ella, un punto no cruza un salto, lo que explica un número sorprendente de preguntas del tipo "¿por qué mi patrón se para al final de la línea?".

u activa el manejo correcto de Unicode. Sin ella, el motor trabaja en unidades de código UTF-16, así que un solo emoji cuenta como dos caracteres y una clase de caracteres puede partirlo por la mitad. Si tu texto tiene algo más allá del plano multilingüe básico, quieres u.

Cuándo no usar una regex

Las regex encuentran patrones en texto plano. No saben contar, no recuerdan anidamientos arbitrarios y no tienen ningún concepto de estructura. HTML, JSON y el código fuente se anidan todos, y por eso todo intento de analizarlos con una regex funciona con los ejemplos y falla con los datos reales. Usa un analizador: todos los lenguajes tienen uno.

Las direcciones de correo merecen su propia advertencia. La gramática del RFC 5322 permite comentarios, cadenas entrecomilladas y construcciones anidadas, y las regex que la implementan fielmente llegan a miles de caracteres. Mientras tanto, una dirección válida puede rebotar igualmente, y una de aspecto "inválido" puede ser perfectamente entregable. Comprueba que haya una @ con algo a ambos lados y luego envía un correo de confirmación: es la única prueba que demuestra algo de verdad.

Preguntas frecuentes

¿Se envían mi patrón o mi texto a un servidor?

No. Esta herramienta está marcada como "client": el patrón se ejecuta en un Web Worker dentro de tu propio navegador, que es un hilo aparte en tu máquina, no una remota. No se transmite nada y no se guarda nada.

¿Por qué me sale "Este patrón tarda demasiado"?

Tu patrón ha superado el presupuesto de un segundo, lo que en la práctica siempre significa retroceso catastrófico y no un trabajo genuinamente grande. Busca cuantificadores anidados —un grupo que termina en + o * y que a su vez se repite, como (a+)+ o (\d*)*— y alternativas cuyas ramas puedan coincidir con el mismo texto. Hacer más específica la parte interior suele hundir el tiempo de minutos a microsegundos.

Mi patrón funciona aquí pero no en mi código. ¿Por qué?

Dos sospechosos habituales. El primero es lastIndex, descrito arriba: tu código probablemente reutiliza un único objeto regex con bandera g donde esta herramienta construye uno nuevo cada vez. El segundo es el escapado. En un literal de cadena de JavaScript, "\d" es solo "d": necesitas "\\d", o mejor un literal de regex como /\d+/ donde no hay ninguna capa de escapado de cadena. El campo de patrón de aquí toma el patrón en bruto, sin ninguna capa de cadena de por medio.

¿Cómo busco un punto literal o un carácter especial?

Ponle una barra invertida delante: \. coincide con un punto en vez de con "cualquier carácter". Los caracteres que necesitan este trato son . * + ? ^ $ { } ( ) | [ ] \ — y dentro de una clase de caracteres las reglas se relajan, así que [.] también funciona y suele leerse mejor. Si estás escapando una cadena que vino de la entrada del usuario, hazlo por código y no a mano.

¿Por qué $ no coincide al final de cada línea?

Porque sin la bandera m, ^ y $ se anclan a toda la cadena en lugar de a cada línea. Añade m a tus banderas y coincidirán en cada límite de línea. Esto hace tropezar constantemente a quien prueba un patrón contra entrada multilínea pegada, porque el patrón está bien y las banderas no.