Probador de expresiones regulares
Pruebe un patrón con texto real, con coincidencias resaltadas y una parada brusca en un retroceso desbocado.
Por qué un probador de expresiones regulares necesita un worker
Casi toda la maquinaria de esta página existe por un único modo de fallo. Piensa en /(a+)+b/ aplicado a treinta a y ninguna b. El motor tiene que decidir cómo repartir esos treinta caracteres entre el cuantificador interior y el exterior, y hay 2³⁰ maneras de hacerlo. Se prueban todas antes de poder declarar imposible la coincidencia.
Lo importante es que una expresión regular en marcha no se puede interrumpir. JavaScript es de un solo hilo, así que una guardia con setTimeout no puede dispararse hasta que la expresión regular devuelva el control, lo que para esa entrada cae más allá de la muerte térmica del universo. La pestaña está perdida.
Por eso el patrón se ejecuta en un Web Worker, en su propio hilo, y la página principal sostiene un temporizador. Cuando gana el temporizador, worker.terminate() mata el hilo sin contemplaciones. Ese es el único mecanismo que funciona de verdad, y es la razón de que este probador te diga que tu patrón es peligroso en lugar de convertirse en una pestaña colgada.
Conviene interiorizarlo, porque el mismo patrón en código de producción es una denegación de servicio: entrada suministrada por el usuario contra una expresión regular vulnerable, y una sola petición clava un núcleo de CPU indefinidamente. A esa clase de fallo se la llama ReDoS.
Las formas que hay que evitar
| Peligroso | Por qué | Más seguro |
|---|---|---|
(a+)+ | Cuantificadores anidados: repartos exponenciales | a+ |
(\w|\s)* | Alternancia dentro de un cuantificador, con ramas que se solapan | [\w\s]* |
(\d+)*$ | Grupo cuantificado con un ancla que fuerza la exploración completa | \d*$ |
.*.*= | Dos comodines sin límite compitiendo por el mismo texto | [^=]*= |
El factor común es la ambigüedad: más de una forma de que el patrón consuma los mismos caracteres. Una clase de caracteres hace el trabajo de una alternancia sin la ambigüedad, y por eso la columna de la derecha consiste sobre todo en cambiar | por [].
Voraz, perezoso y el error que todo el mundo escribe una vez
Los cuantificadores son voraces por defecto: .+ se lleva todo lo que puede y devuelve caracteres solo a regañadientes. Contra <b>bold</b>:
| Patrón | Coincide con |
|---|---|
<.+> | <b>bold</b>: todo entero |
<.+?> | <b>: perezoso, se detiene en el primer > |
<[^>]+> | <b>: y más rápido, porque no hay nada que deshacer |
La tercera forma es la que hay que usar. Una clase de caracteres negada no puede pasarse de largo, así que no hay retroceso que deshacer: es a la vez más clara en cuanto a la intención y más barata de ejecutar que la versión perezosa.
lastIndex, y por qué desaparece una de cada dos coincidencias
Un RegExp con la marca g tiene estado. Guarda un lastIndex que avanza tras cada exec o test, así que reutilizar un mismo objeto en llamadas distintas reanuda desde donde se quedó:
const re = /\d+/g;
re.test('123'); // true, lastIndex is now 3
re.test('123'); // false! it resumed from index 3Crea la expresión regular dentro del bucle, reinicia lastIndex = 0 o usa matchAll, que se encarga por ti. El probador de arriba crea un objeto nuevo en cada ejecución, así que lo que ves aquí es el comportamiento de la primera llamada.
En qué se diferencian las expresiones regulares de JavaScript
Los resultados de aquí coinciden exactamente con los de Node y los del código del navegador, porque es el mismo motor. Frente a otros lenguajes, las diferencias que pillan a la gente:
- Sin grupos atómicos ni cuantificadores posesivos. PCRE tiene
(?>...)ya++para impedir el retroceso de raíz; JavaScript no tiene ninguno de los dos, y por eso aquí es más fácil escribir un ReDoS. - Sin recursión. No puedes casar paréntesis equilibrados con una expresión regular de JavaScript. Si ese es el requisito, necesitas un analizador sintáctico.
- La búsqueda hacia atrás llegó tarde.
(?<=...)está soportada en los navegadores actuales pero no en las versiones antiguas de Safari, así que comprueba tus objetivos. - RE2 de Go es otra máquina. Tiempo lineal garantizado, sin retrorreferencias y sin búsquedas alrededor. Si alguna vez te has preguntado por qué Go dejó fuera esas funciones, esta página es la razón.
Relacionado
Si el texto contra el que estás casando es JSON, formatearlo primero suele hacer innecesario el patrón: con datos estructurados, un analizador gana siempre a una expresión regular. Para programar en vez de casar, la herramienta vecina es el analizador de cron.
Preguntas sobre expresiones regulares
¿Por qué mi patrón se detuvo después de dos segundos?
Porque estaba retrocediendo catastróficamente. Ciertas formas (cuantificadores anidados como (a+)+ o (\w|\s)*) hacen que el motor intente un número exponencial de formas de hacer coincidir, por lo que una cadena de cuarenta caracteres puede tardar más de lo que ha existido el universo. No hay forma de interrumpir una expresión regular en ejecución desde JavaScript, razón por la cual el patrón se ejecuta aquí en un Web Worker: el trabajador puede finalizarse y la página sigue respondiendo. Un tiempo de espera en el hilo principal solo se activaría una vez que terminara la expresión regular, lo que para esos patrones nunca ocurre.
¿Utiliza esto el mismo motor de expresiones regulares que mi código?
Utiliza el motor JavaScript de su navegador, por lo que los resultados coinciden exactamente con el código del nodo y del navegador. No siempre coincidirán con PCRE, Python, Go o Java. JavaScript no tiene grupos atómicos, cuantificadores posesivos ni recursividad; Lookbehind es compatible con los navegadores actuales, pero llegó tarde. El RE2 de Go es un diseño completamente diferente y deliberadamente no tiene referencias hacia atrás, que es precisamente como evita el problema de retroceso mencionado anteriormente.
¿Por qué mi expresión regular global sólo encuentra coincidencias entre sí?
Un objeto RegExp con el indicador g lleva un lastIndex que avanza con cada llamada, por lo que la reutilización del mismo objeto en llamadas ejecutivas o de prueba separadas se reanuda desde donde lo dejó. Es una de las partes más confusas de la API. Cree la expresión regular nueva cada vez, restablezca lastIndex a 0 antes de usarla o use matchAll, que lo maneja por usted.
¿Cuál es la diferencia entre un cuantificador codicioso y uno perezoso?
Un cuantificador codicioso toma todo lo que puede y devuelve caracteres sólo cuando se le obliga; uno perezoso (¿escrito con un final?) toma lo menos posible y se expande solo cuando es necesario. Contra <b>negrita</b> el patrón <.+> coincide con toda la cadena, porque .+ se traga todo y retrocede lo suficiente para encontrar un > final. Escribir <.+?> coincide solo con <b>. Ésta es la razón más común por la que un patrón coincide mucho más de lo previsto.
¿Es seguro pegar datos reales aquí?
Sí. El patrón y el texto se entregan a un trabajador dentro de esta página y se evalúan allí. No se envía nada a un servidor, lo que también hace posible cancelar un patrón descontrolado, ya que no hay ninguna solicitud que esperar.
Última revisión . ¿Has visto algo desactualizado? Dínoslo.
