Cómo ser feliz usando Regex

2020-05-12

post-thumb

Índice

Regex, motherfucker

En este artículo, haremos que tus ojos lloren sangre. De alegría.

Las expresiones regulares (regex o regexp) son extremadamente útiles para extraer información de cualquier texto, buscando una o más coincidencias de un patrón de búsqueda específico (es decir, una cadena específica de caracteres ASCII o unicode).

Los campos de aplicación van desde la validación hasta el análisis/reemplazo de strings, pasando por la traducción de datos a otros formatos y scraping web.

Una de las características más interesantes es que, una vez que aprendas la sintaxis, podrás usar esta herramienta en (casi) todos los lenguajes de programación (JavaScript, Golang, Kotlin, Java, C#, Python, Perl y muchos otros) con muy pocas diferencias en cuanto a características y versiones de sintaxis.

Comencemos examinando algunos ejemplos y explicaciones.

Temas Básicos

Anclas — ^ y $

RegexResultado
^HomerCorresponde a cualquier secuencia que comience con Homer
Marge$Corresponde a una cadena que termina con Marge
^Bart Simpson$Correspondencia exacta de cadena (comienza y termina con Bart Simpson )
LisaCorresponde a cualquier cadena que tenga el texto Lisa

Cuantificadores — * + ? y

RegexResultado
abc*Corresponde a una cadena que tiene ab seguido de cero o más c
abc+Corresponde a una cadena que tiene ab seguido de uno o más c
abc?Corresponde a una cadena que tiene ab seguido de cero o un c
abc{2}Corresponde a una cadena que tiene ab seguido de 2 c
abc{2,}Corresponde a una cadena que tiene ab seguido de 2 o más c
abc{2,5}Corresponde a una cadena que tiene ab seguido de 2 hasta 5 c
a(bc)*Corresponde a una cadena que tiene a seguido de cero o más copias de la cadena bc
a(bc){2,5}Corresponde a una cadena que tiene a seguido de 2 hasta 5 copias de la cadena bc

Operador OR — | o []

RegexResultado
a(b|c)Corresponde a una cadena que tiene a seguido de b o c (y captura b o c)
a[bc]Igual al anterior, pero sin capturar b o c

Clases de caracteres — \d \w \s y .

RegexResultado
\dCorresponde a un solo carácter que es un dígito
\wCorresponde a un carácter de palabra (carácter alfanumérico más guion bajo)
\sCorresponde a un carácter de espacio en blanco (incluye tabulaciones y saltos de línea)
.Corresponde a cualquier carácter

Caracteres negados

Usa el . con cuidado, ya que muchas veces la clase o la clase de caracteres negada (que abordaremos a continuación) son más rápidas y precisas.

\d, \w y \s también presentan sus negaciones con \D, \W y \S respectivamente.

Por ejemplo, \D ejecutará la correspondencia inversa (corresponde a un solo carácter sin dígito) en relación a la obtenida con \d.

Escapando literales

Regex power

Para ser considerado literalmente, debes escapar los caracteres ^ . [ $ ( ) | * + ? { \ con una barra invertida \, ya que tienen un significado especial. Al escapar el carácter, puedes encontrar un $ antes de un dígito .

También es posible encontrar caracteres no imprimibles, como tabulaciones \t, nuevas líneas \n y retornos \r.

Flags

Una regex generalmente se escribe en este formato /abc/, donde el patrón de búsqueda está delimitado por dos caracteres de barra /. Al final, podemos especificar una flag con los siguientes valores (que pueden combinarse) =

  • g (global) = no retorna después de la primera coincidencia, reiniciando las búsquedas subsiguientes desde el final de la coincidencia anterior
  • m (varias líneas) = cuando está activado ^ y $ corresponderán al inicio y al final de una línea, en lugar de toda la cadena
  • i (insensible) = hace que la expresión completa no distinga entre mayúsculas y minúsculas (por ejemplo /aBc/i encontraría AbC)

Temas intermedios

Agrupando y capturando — ()

RegexResultado
a(bc)Los paréntesis crean un grupo de captura con el valor bc
a(? =bc)*Usando ? = desactivamos el grupo de captura
a(?<foo>bc)Usando ?<foo> colocamos un nombre al grupo
Regex Yoda

Este operador es muy útil cuando necesitamos extraer información de cadenas o datos usando tu lenguaje de programación preferido. Cualquier ocurrencia múltiple capturada por varios grupos será expuesta en forma de un array clásico = accederemos a sus valores especificando un índice en el resultado de la coincidencia.

Si elegimos nombrar los grupos que utilizaremos (como (?<foo>...)), podremos recuperar los valores del grupo usando el resultado de la coincidencia como un diccionario/map donde las claves serán el nombre de cada grupo.

Expresiones entre corchetes — []

RegexResultado
[abc]Corresponde a una cadena que tiene a o b o c (lo mismo que a|b|c)
[a-c]Lo mismo que el anterior
[a-fA-F0-9]Una cadena que representa un solo dígito hexadecimal, sin distinción entre mayúsculas y minúsculas
[0-9]%Una cadena que tiene un carácter de 0 a 9 antes de un signo de %
[^a-zA-Z]Una cadena que no tiene una letra de a a z o de A a Z . En este caso, el ^ se usa como negación de la expresión

Recuerda que dentro de las expresiones entre corchetes todos los caracteres especiales (incluida la barra invertida \) pierden sus poderes especiales = por lo tanto, no aplicaremos la regla de escape.

Coincidencias greedy y lazy

Regex death

Los cuantificadores * + {} son operadores codiciosos (greedy), por lo que expanden la coincidencia lo máximo posible a través del texto proporcionado. Por ejemplo, <.+> corresponde a <div>simple div</div> en Esta es una prueba de <div>simple div</div>. Para capturar solo la etiqueta div, podemos usar un ? para hacerlo perezoso (lazy) =

<.+?> corresponde a cualquier carácter una o más veces incluido dentro de < y >, expandiéndose según sea necesario

Ten en cuenta que una mejor solución debe evitar el uso de . en favor de una regex más estricta =

<[^<>]+> corresponde a cualquier carácter , excepto < o > una o más veces incluidos dentro de < y >


Temas avanzados

Límites - \b y \B

\babc\b corresponde a una búsqueda de solo palabras completas

\b representa un ancla como el signo de intercalación (es similar a $ y ^) que coincide con posiciones donde un lado es un carácter de palabra (como \w) y el otro lado no es un carácter de palabra (por ejemplo, puede ser el inicio de la cadena o un carácter de espacio)

Tiene una negación, \B. Es decir, el resultado de todas las posiciones donde \b no coincide y podría estar si queremos encontrar un patrón de búsqueda completamente rodeado por caracteres de palabra.

Back-references — \1

RegexResultado
([abc])\1Usando \1, coincide con el mismo texto que fue coincidido por el primer grupo de captura
([abc])([de])\2\1Podemos usar \2 (\3, \4, etc.) para identificar el mismo texto que fue coincidido por el segundo (tercero, cuarto, etc.) grupo de captura
(?<foo>[abc])\k<foo>Colocamos el nombre foo en el grupo y lo referenciamos después (\k<foo>). El resultado es el mismo que el primer regex

Look-ahead y Look-behind — (?=) y (?<=)

e(?=r) Corresponde a e solo si es seguido por r, pero r no será parte de la coincidencia general de la expresión regular (?<=r)i Corresponde a i solo si es precedido por un r, pero r no será parte de la coincidencia general de la expresión regular

RegexResultado
e(?=r)Corresponde a e solo si es seguido por r, pero r no será parte de la coincidencia general de la expresión regular
(?<=r)iCorresponde a i solo si es precedido por un r, pero r no será parte de la coincidencia general de la expresión regular

Y al usar el operador de negación =

RegexResultado
e(?!r)Corresponde a e solo si no es seguido por r, pero r no será parte de la coincidencia general de la expresión regular
(?<!c)iCorresponde a i solo si no es precedido por un c, pero c no será parte de la coincidencia general de la expresión regular

Herramientas útiles

Regex, Neo
  • Regex101 es un sitio donde puedes visualizar tus regexes de una forma didáctica. Además de poder exportar fragmentos de código para tu lenguaje favorito.

  • Hacer tablas en markdown (como este sitio está escrito) es complicado, pero gracias a Markdown Tables la tarea se vuelve más fácil


Conclusión

El poder de la regex no puede ser subestimado, joven saltamontes. Los campos de aplicación de regex pueden ser múltiples y estoy seguro de que has notado al menos una de estas tareas durante tu carrera como desarrollador =

  • validación de datos = por ejemplo, verifica si una fecha o un correo electrónico son válidos
  • scraping de datos = especialmente scraping web, encuentra todas las páginas que contienen un determinado conjunto de palabras, eventualmente en un orden específico
  • disputa de datos = transformar datos “crudos” a otro formato
  • análisis de cadenas = por ejemplo, obtener todos los parámetros GET de una URL, capturar texto dentro de un conjunto de paréntesis
  • reemplazo de cadenas de caracteres = reemplazar , por ;, convertir a minúsculas, etc.
  • resaltado de sintaxis, renombrado de archivos y muchas otras cosas que involucran cadenas

UPDATE = Escribí un nuevo artículo con las regexes más usadas, que puedes ver aquí = Regex útiles para tu día a día