Una expresión regular (en inglés, regular expression, y a menudo regex) es un patrón que describe una familia de textos con unos pocos símbolos, para encontrarlos, comprobarlos o cambiarlos de golpe. El patrón \d{5} quiere decir «cinco cifras seguidas» y encuentra todos los códigos postales de un documento; \d+,\d{2} ?€ encuentra todos los precios con céntimos. No hace falta saber programar para usarlas: las entienden el buscar y reemplazar de casi cualquier editor de texto, las hojas de cálculo de Google con la función REGEXMATCH y los filtros del correo, y son la herramienta que más trabajo manual ahorra por minuto aprendido.
La figura de esta página es un banco de pruebas. Arriba se escribe el patrón y el texto donde buscar; en el texto se iluminan las coincidencias, y debajo aparecen dos tablas. La primera desmonta el patrón pieza a pieza y dice qué pide cada una. La segunda lista lo encontrado, con su posición y lo que ha capturado cada grupo entre paréntesis. Los ejemplos del desplegable cubren los casos de todos los días: direcciones de correo, fechas, precios, DNI y códigos postales. El último es una trampa.
El vocabulario básico cabe en un párrafo. Una letra o una cifra se busca a sí misma. \d es cualquier cifra, \w cualquier letra, cifra o guion bajo, \s un espacio y el punto, cualquier carácter. Los corchetes ofrecen una elección: [aeiou] es una vocal y [0-9A-F], una cifra hexadecimal. Detrás de cualquier pieza, + quiere decir «una vez o más», * «cero o más», ? «opcional» y {2,4} «de dos a cuatro veces». Los paréntesis agrupan y, además, guardan lo que encuentran, y eso es lo que hace útil el reemplazo. Con el patrón de fechas de la figura, que captura el día, el mes y el año en tres grupos, reemplazar por $3-$2-$1 convierte de golpe todas las fechas de un documento del formato español al internacional.
Los ejemplos enseñan también el límite más importante: un patrón comprueba la forma, no el sentido. El de las fechas acepta un 31/02/2024, que tiene el aspecto de una fecha y no existe. El del DNI pide ocho cifras y una letra de las que usa la policía, que excluye la I, la Ñ, la O y la U para no confundirlas, pero no sabe si la letra es la que corresponde a esas cifras, porque eso se calcula con el resto de dividir el número entre 23 y una expresión regular no divide. El de los correos acepta direcciones razonables y rechaza «juan@@mal», pero la definición completa de una dirección válida ocupa varias páginas de norma, y los programadores prácticos no intentan escribirla. La manera fiable de saber si un correo existe es mandar un mensaje y pedir que se confirme.
La idea es de un matemático. Stephen Kleene, que estudiaba qué podían calcular las redes de neuronas idealizadas de McCulloch y Pitts, describió en un informe de 1951 para la corporación RAND, publicado en 1956, una notación para los «sucesos regulares», las secuencias que puede reconocer una máquina con una memoria finita, lo que hoy se llama un autómata finito. Demostró que su notación y esas máquinas describen exactamente lo mismo. Ken Thompson la llevó al ordenador en 1968: en un artículo breve en Communications of the ACM explicó cómo traducir un patrón a un pequeño programa que lee el texto una sola vez, y lo metió en el editor de textos QED. Años después, ya en Unix, un comando del editor ed, g/re/p («buscar globalmente una expresión regular e imprimir»), se convirtió en un programa propio, grep, que todavía se usa a diario.
El último ejemplo de la figura enseña lo que se perdió por el camino. El patrón ^(a+)+$ pide un texto hecho solo de aes, y el texto son treinta y una aes y una be. La respuesta es obviamente no, pero el motor que llevan JavaScript, Python, Java y la mayoría de lenguajes no lo ve así. No sigue el método de Thompson sino otro más flexible, el de ir probando y retroceder cuando falla (backtracking), y aquí tiene que probar todas las maneras de repartir las aes entre las repeticiones del grupo antes de rendirse: con treinta y una aes, más de mil millones. Cada letra más duplica el tiempo, y la figura corta la búsqueda al pasar de 400 milisegundos. Russ Cox, que después diseñaría el motor de expresiones regulares del lenguaje Go, documentó en 2007 que un motor como el de Thompson resuelve el mismo caso en microsegundos.
El 2 de julio de 2019 esa diferencia dejó sin servicio a una parte enorme de la web. Cloudflare, que sirve como intermediario a millones de sitios, publicó una regla nueva para su cortafuegos que contenía una expresión regular con dos .* seguidos, una combinación que obliga a retroceder mucho con ciertos textos. Según explicó John Graham-Cumming, entonces su director técnico, la regla agotó la capacidad de proceso de todos los servidores que atendían tráfico web en su red mundial, y durante veintisiete minutos cualquiera que visitara un sitio de sus clientes recibió una página de error. La empresa retiró la regla y anunció que cambiaría a un motor que no retrocede.
La conclusión práctica no es dejar de usarlas, sino usarlas con tres cuidados. Probar el patrón contra ejemplos buenos y malos antes de lanzarlo sobre un documento entero, que es lo que permite la figura; un reemplazo con un patrón demasiado generoso se come cosas que no debía. Preferir patrones concretos a patrones vagos: [^,]* («todo lo que no sea una coma») retrocede mucho menos que .*. Y no pedirles lo que no saben hacer: comprobar el sentido de una fecha, la letra de un DNI o la existencia de un correo es trabajo para otra herramienta. Para todo lo demás, que es mucho, pocas líneas de texto hacen tanto.
Autómata computacional – La máquina de Turing – La notación O grande – El compilador – La fuerza de una contraseña – El control de versiones
Fuentes
- Stephen C. KleeneRepresentation of Events in Nerve Nets and Finite AutomataAutomata Studies (Annals of Mathematics Studies 34), Princeton University Press1956enlace
- Ken ThompsonProgramming Techniques: Regular expression search algorithmCommunications of the ACM 11 (6)1968enlace
- Russ CoxRegular Expression Matching Can Be Simple And Fastswtch.com2007enlace
- John Graham-CummingDetails of the Cloudflare outage on July 2, 2019The Cloudflare Blog2019enlace