¿Cómo se usa realmente Regex?

Regex

, abreviatura de expresión regular, se usa a menudo en lenguajes de programación para hacer coincidir patrones en cadenas, buscar y reemplazar, validación de entrada y reformateo de texto.

Aprender a usar Regex correctamente puede hacer que trabajar con texto sea mucho más fácil.

La sintaxis de Regex explicada

Regex tiene la reputación de tener una sintaxis horrenda, pero es mucho más fácil de escribir que de leer.

Por ejemplo, aquí hay una expresión regular general para un validador de correo electrónico compatible con RFC 5322 :

(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/= ?^_`{|}~-]+)*|"(?:[x01-
x08x0bx0cx0e-x1fx21x23-x5bx5d-x7f]|\[x01-x09x0bx0cx0e-x7f])*")
@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a ) -z0-9-]*[a-z0-9])?|[(?
:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-
9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[x01-x08x0bx0cx0e-x1fx21-x5ax53-x7f]|
\[x01-x09x0bx0cx0e-x7f])+)])

Si te da la sensación de que alguien se golpeó la cara contra el teclado, no serás al único. Pero debajo del código, todo ese lío en realidad está programando una máquina de estado finito.

Esta máquina se ejecuta para cada personaje, avanzando y emparejando según las reglas que hayas establecido.

Son máquinas con partes móviles que tienen reglas que definen cómo encaja todo.

En primer lugar: Usa un depurador Regex

Antes de comenzar, a menos que tu Regex sea particularmente corto o excepcionalmente decente, debes usar un depurador en línea para escribirlo y probarlo.

Eso hace que la comprensión de la sintaxis sea mucho más fácil. Son muy recomendables Regex101 y RegExr que ofrecen pruebas y referencia de sintaxis integrada.

¿Cómo funciona la expresión regular?

Por ahora, veamos algo simple. Este es un diagrama de Regulex para un Regex de coincidencia de correo electrónico muy corto (y definitivamente no compatible con RFC 5322).

El motor Regex comienza a la izquierda y viaja a lo largo de las líneas, haciendo coincidir los caracteres a medida que avanza. El grupo n.º 1 coincide con cualquier carácter, excepto con un salto de línea, y seguirá haciendo coincidir caracteres hasta que el siguiente bloque encuentre una coincidencia.

En este caso, se detiene cuando llega a un símbolo @ , lo que significa que el Grupo #1 captura el nombre de la dirección de correo electrónico y todo lo que sigue coincide con el dominio.

El Regex que define el Grupo #1 en nuestro ejemplo de correo electrónico es:

(.+)

Los paréntesis definen un grupo de captura, que le dice al motor Regex que incluya el contenido de la coincidencia de este grupo en una variable especial.

Cuando ejecutas un Regex en una cadena, el resultado predeterminado es la coincidencia completa (en este caso, todo el correo electrónico). Pero también devuelve cada grupo de captura, lo que hace que este Regex sea útil para extraer nombres de correos electrónicos.

El punto es el símbolo de “cualquier carácter excepto nueva línea”. Eso coincide con todo en una línea, por lo que si pasas el siguiente correo electrónico a Regex:

%$#^&%*#%$#^@gmail.com

El nombre coincidirá con %$#^&%*#%$#^ , aunque eso sea ridículo.

El símbolo más (+) es una estructura de control que significa “coincidir con el carácter o grupo anterior una o más veces”. Eso garantiza que coincida todo el nombre y no solo el primer carácter.

Esto es lo que crea el bucle que se encuentra en el diagrama del ferrocarril. El resto de Regex es bastante simple de descifrar:

(.+)@(.+..+)

El primer grupo se detiene cuando toca el símbolo @ . Luego comienza el siguiente grupo que nuevamente coincide con varios caracteres hasta que llega a un carácter de punto.

Debido a que los caracteres como puntos, paréntesis y barras se usan como parte de la sintaxis en Regex, cada vez que deseas hacer coincidir esos caracteres, debes escaparlos correctamente con una barra invertida.

En este ejemplo, para hacer coincidir el período, escribimos . y el analizador lo trata como un símbolo que significa “coincidir con un período”.

Coincidencia de personajes

Si tienes caracteres que no son de control en tu código de Regex, el motor Regex asumirá que esos caracteres formarán un bloque coincidente.

Por ejemplo, la expresión regular:

ho+la

Hará coincidir la palabra “hola” con cualquier número de o. Cualquier otro carácter debe escaparse para que funcione correctamente.

Regex también tiene clases de caracteres, que actúan como abreviaturas de un conjunto de caracteres. Estos pueden variar según la implementación de Regex, pero estos pocos son estándar:

  • . – coincide con cualquier cosa excepto con la nueva línea.
  • w – coincide con cualquier carácter de “palabra”, incluidos dígitos y guiones bajos.
  • d – coincide con los números.
  • b – coincide con caracteres de espacio en blanco (es decir, espacio, tabulador, nueva línea).

Estos tres tienen contrapartes en mayúsculas que invierten su función. Por ejemplo, D coincide con cualquier cosa que no sea un número.

Regex también tiene coincidencias de conjuntos de caracteres. Por ejemplo:

[abc]

Coincidirá con a , b o c . Eso actúa como un bloque y los corchetes son solo estructuras de control.

Alternativamente, puedes especificar un rango de caracteres:

[a-c]

O negar el conjunto que coincidirá con cualquier carácter que no esté en el conjunto:

[^a-c]

Cuantificadores

Los cuantificadores son una parte importante de Regex, permiten hacer coincidir cadenas en las que no se conoce el formato exacto pero si tienes una idea bastante aproximada.

El operador + del ejemplo de correo electrónico es un cuantificador, específicamente el cuantificador “uno o más”.

Si no sabemos la longitud de una determinada cadena, pero sabemos que está formada por caracteres alfanuméricos (y no está vacía), podemos escribir:

w+

Además de + también existen los cuantificadores siguientes:

  • El operador * que coincide con “cero o más”. Esencialmente lo mismo que + , excepto que tiene la opción de no encontrar una coincidencia.
  • El operador ? que coincide con “cero o uno” y tiene el efecto de hacer que un personaje sea opcional; o está ahí o no está y no coincidirá más de una vez.
  • Cuantificadores numéricos: Estos pueden ser un solo número como {3} , que significa “exactamente 3 veces” o un rango como {3-6} . Puedes omitir el segundo número para que sea ilimitado. Por ejemplo, {3,} significa “3 o más veces”. Por extraño que parezca, no puedes omitir el primer número, por lo que si deseas “3 veces o menos” tendrá que usar un rango.

Cuantificadores codiciosos y perezosos

Debajo del código, los operadores * y son codiciosos. Coincide tanto como sea posible y devuelve lo que se necesita para comenzar el siguiente bloque.

Esto puede ser un problema masivo +

Aquí hay un ejemplo: Supongamos que estás tratando de hacer coincidir HTML o cualquier otra cosa con llaves de cierre. Su texto de entrada es:

<div>Hola mundo</div>

Si deseas hacer coincidir todo lo que está dentro de los corchetes. Puedes escribir algo como:

<.*>

Esta es la idea correcta, pero falla por una razón crucial: El motor de Regex busca coincidencias con ” div&gt;Hola mundo</div> ” para la secuencia .* y luego retrocede hasta que el siguiente bloque coincide, en este caso, con un paréntesis de cierre ( &gt; ).

Esperaría que retrocediera para coincidir solo con “ div ” y luego se repita nuevamente para coincidir con el div de cierre.

Pero el backtracker se ejecuta desde el final de la cadena y se detendrá en el corchete final, que termina haciendo coincidir todo lo que está dentro de los corchetes.

La solución es hacer que nuestro cuantificador sea perezoso, lo que significa que coincidirá con la menor cantidad de caracteres posible.

Esto en realidad solo coincidirá con un carácter y luego se expandirá para llenar el espacio hasta la siguiente coincidencia de bloque, lo que lo hace mucho más eficaz en grandes operaciones Regex.

Si quieres hacer que un cuantificador sea perezoso debes agregare un signo de interrogación directamente después del cuantificador.

Esto es un poco confuso porque ? ya es un cuantificador (y en realidad es codicioso por defecto), por ejemplo en nuestro ejemplo de HTML, Regex se corrigiría con esta simple adición:

<.*?>

El operador perezoso se puede agregar a cualquier cuantificador, incluidos +? , {0,3}? e incluso ?? . Aunque el último no tiene ningún efecto; debido a que está haciendo coincidir cero o un carácter de todos modos y no existe espacio para expandirse.

Agrupación y Lookarounds

Los grupos en Regex tienen muchos propósitos. En un nivel básico, unen varios tokens en un solo bloque.

Por ejemplo, puedes crear un grupo y luego usar un cuantificador en todo el grupo:

ba(na)+

Esto agrupa el “na” repetido para que coincida con la frase banana , y banananana y así sucesivamente. Sin el grupo, el motor Regex solo coincidiría con el carácter final una y otra vez.

Este tipo de grupo con dos paréntesis simples se denomina grupo de captura y lo incluye en la salida.

Si deseas evitar esto y simplemente agrupar tokens por motivos de ejecución, puede usar un grupo que no capture:

ba(?:na)

El signo de interrogación (un carácter reservado) define un grupo no estándar y el siguiente carácter define qué tipo de grupo es. Comenzar grupos con un signo de interrogación es ideal, porque de lo contrario, si quisieras hacer coincidir los puntos y comas en un grupo, necesitarías escaparlos sin una buena razón.

Pero siempre tienes que escapar los signos de interrogación en Regex.

También puedes nombrar tus grupos, para mayor comodidad, cuando trabajes con la salida:

(?'group')

Puedes hacer referencia a esto en tu Regex, lo que los hace funcionar de manera similar a las variables. Puedes hacer referencia a grupos sin nombre con el token 1 , pero eso solo llega a 7, después de lo cual deberás comenzar a nombrar grupos.

La sintaxis para hacer referencia a grupos con nombre es:

k{group}

Esto hace referencia a los resultados del grupo nombrado, que puede ser dinámico. Esencialmente, verifica si el grupo aparece varias veces pero no le importa la posición.

Por ejemplo, esto se puede usar para hacer coincidir todo el texto entre tres palabras idénticas.

La clase de grupo es donde encontrarás la mayor parte de la estructura de control de Regex, incluidas las búsquedas anticipadas.

Las búsquedas anticipadas garantizan que una expresión debe coincidir, pero no la incluye en el resultado. En cierto modo, es similar a una declaración if y no coincidirá si devuelve falso.

La sintaxis para una anticipación positiva es (?=) .

Esto coincide con la parte del nombre de una dirección de correo electrónico muy limpiamente, deteniendo la ejecución en la división @ .

Las búsquedas anticipadas no consumen ningún carácter, por lo que si deseas continuar ejecutarla después de que una búsqueda anticipada tenga éxito, aún puedes hacer coincidir el carácter utilizado en la búsqueda anticipada.

Además de los pronósticos positivos, también tienes:

  • (?!) Previsiones negativas, que aseguran que una expresión no coincida.
  • (?&lt;=) Lookbehinds positivos, que no se admiten en todas partes debido a algunas limitaciones técnicas. Estos se colocan antes de la expresión que deseas hacer coincidir y deben tener un ancho fijo (es decir, sin cuantificadores excepto {number} . En este ejemplo, podrías usar (?&lt;=@)w+.w+ para hacer coincidir la parte del dominio del correo electrónico.
  • (?&lt;!) – Lookbehinds negativos que son iguales a los lookbehinds positivos, pero negados.

Diferencias entre motores Regex

No todos los motores de Regex son iguales. La mayoría de los motores Regex no siguen ningún estándar específico y algunos cambian un poco las cosas para adaptarse a su idioma.

Algunas características que funcionan en un idioma pueden no funcionar en otro.

Por ejemplo, las versiones de sed compilado para macOS y FreeBSD no admiten el uso t para representar un carácter de tabulación. Tienes que copiar manualmente un carácter de tabulación y pegarlo en la terminal para usar una tabulación en la línea de comandos sed .

La mayor parte de este tutorial es compatible con PCRE, el motor Regex predeterminado que se usa para PHP.

Pero el motor Regex de JavaScript es diferente: No admite grupos de captura con nombre entre comillas (quiere corchetes) y no puede hacer recursividad, entre otras cosas.

Incluso PCRE no es completamente compatible con diferentes versiones y tiene muchas diferencias con las expresiones regulares de Perl.

Hay demasiadas diferencias menores para enumerarlas aquí, por lo que puedes usar esta tabla de referencia para comparar las diferencias entre varios motores Regex.

Además, los depuradores Regex como Regex101 permiten cambiar los motores Regex, así que asegúrate de que estás depurando con el motor correcto.

¿Cómo ejecutar expresiones regulares?

Hemos estado viendo la parte coincidente de las expresiones regulares que constituye la mayor parte de lo que hace un Regex.

Pero cuando realmente desees ejecutar Regex, deberás convertirlo en una expresión regular completa.

Esto generalmente toma el formato de:

/match/g

Lo que está dentro de las barras diagonales es nuestra coincidencia. El g es un modificador de modo. En este caso, se encarga de decir al motor que no deje de funcionar después de encontrar la primera coincidencia.

Si quieres buscar y reemplazar Regex, a menudo tendrás que formatearlo como:

/find/replace/g

Eso reemplaza todo en todo el archivo. Puedes usar referencias de grupos de captura al reemplazar, lo que hace que Regex sea muy bueno para formatear texto.

Por ejemplo, el siguiente código de Regex coincidirá con cualquier etiqueta HTML y reemplazará los corchetes estándar por corchetes:

/<(.+?)>/[1]/g

Cuando esto se ejecute, el motor coincidirá con <div> y </div> , lo que le permitirá reemplazar este texto (y solo este texto).

Eso hace que Regex sea muy útil para buscar y reemplazar texto. La utilidad de línea de comando para hacer esto es sed , que usa el formato básico de:

sed '/find/replace/g' file > file

Esto se ejecuta en un archivo y sale a STDOUT. Deberás conectarlo a sí mismo para reemplazar el archivo en el disco.

Regex también es compatible con muchos editores de texto y realmente puede acelerar tu flujo de trabajo cuando realices operaciones por lotes.

Vim, Atom y VS Code tienen incorporado Regex para poder buscar y reemplazar contenidos.

Por supuesto, Regex también se puede usar junto a los códigos de programación y por lo general, está integrado en muchos idiomas.

La implementación exacta dependerá de cada idioma, por lo que debes consultar la documentación de cada uno de los lenguajes de programación.

Por ejemplo, en JavaScript, la expresión regular se puede crear literalmente o dinámicamente usando el objeto RegExp global:

var re = new RegExp('abc')

Esto se puede usar directamente llamando al método del objeto regex .exec() recién creado o usando los métodos .replace() , .match() o .matchAll() en las cadenas.

Relacionados

Deja un comentario