Expresiones regulares
Las expresiones regulares son un potente sistema de descripción de texto, y no existe un lenguaje de programación moderno que no permita usarlas. Las reglas con las que se forman son bastante simples, pero aprender a combinarlas correctamente requiere de práctica.
Utilizándolas podemos buscar una subcadena al principio o al final del texto. Incluso si queremos que se repita cierta cantidad de veces, si queremos que algo NO aparezca, o si debe aparecer una subcadena entre varias posibilidades.
Permite, además, capturar aquellos fragmentos del texto que coincidan con la expresión para guardarlos en una variable o reemplazarlos por una cadena predeterminada; o incluso una cadena formada por los mismos fragmentos capturados. Estos son algunos aspectos básicos de las expresiones regulares.
Cuando manejamos texto, una de las operaciones más comunes es la búsqueda de una subcadena; ya sea para obtener su posición en el texto o simplemente para comprobar si está presente.
Si la cadena que buscamos es fija, los métodos de búsqueda propios del lenguaje utilizado nos ayudarán. Pero si buscamos una subcadena con cierta forma, las expresiones regulares son una completa herramienta que puede facilitarnos el trabajo.
Los patrones de búsqueda nos van a permitir obtener direcciones de correo electrónico, números de teléfono, validar campos de entrada, o una letra mayúscula seguida de dos minúsculas y de 5 dígitos.
Metacaracteres
Se conoce como metacaracteres a aquellos que, dependiendo del contexto, tienen un significado especial para las expresiones regulares. Por lo tanto, los debemos escapar colocándoles una barra invertida (\) delante para buscarlos explícitamente. A continuación, una lista de los más importantes:
Anclas:
Indican que lo que queremos encontrar se encuentra al principio o al final de la cadena. Combinándolas, podemos buscar algo que represente a la cadena entera.
- ^ (Acento circunflejo; coincide con el comienzo de la cadena. Por ejemplo; '^patrón', coincide con cualquier cadena que comience con patrón)
- $ (Dólares; coincide con el final de la cadena. Por ejemplo; 'patrón$', coincide con cualquier cadena que termine con patrón)
- ^patrón$ (Coincide con la cadena exacta patrón)
Clases de caracteres:
Se utilizan cuando se quiere buscar un caracter dentro de varias posibles opciones. Una clase se delimita entre corchetes y lista posibles opciones para el caracter que representa.
- [abc] (coincide con a, b, o c)
- [387ab] (coincide con 3, 8, a o b)
- niñ[oa]s (coincide con niños o niñas)
- mar[íti][ao] (Coincide con maría, con marta y con mario)
Rangos:
Si queremos encontrar un número, podemos usar una clase como [0123456789], o podemos utilizar un rango.
Un rango es una clase de caracteres abreviada que se crea escribiendo el primer caracter del rango, un guión y el último caracter del rango. Múltiples rangos pueden definirse en la misma clase de caracteres.
- [a-c] (equivale a [abc])
- [0-9] (equivale a [0123456789])
- [a-d5-8] (equivale a [abcd5678])
Es importante notar que si se quiere buscar un guión debe colocarse al principio o al final de la clase. Es decir, inmediatamente después del corchete izquierdo o inmediatamente antes del corchete derecho, o en su defecto, escaparse.
Si queremos crear una clase que coincida con los caracteres a, 4 o guión medio(-) sin que haya un error, debemos escribirla de alguna de estas formas:
- [a4-] (Con el guión al final)
- [-a4] (Con el guión al comienzo)
- [a\-4] (Con el guión escapado)
Rango negado:
Así como podemos listar los caracteres posibles en cierta posición de la cadena, también podemos listar caracteres que no deben aparecer. Para lograrlo, debemos negar la clase, colocando un circunflejo inmediatamente después del corchete izquierdo:
- [^abc] (coincide con cualquier caracter distinto a a, b y c)
- [^123] (coincide con cualquier caracter distinto a 1, 2 y 3)
Clases predefinidas
Existen algunas clases que se usan frecuentemente y por eso existen formas abreviadas para ellas. En Python, así como en otros lenguajes, se soportan las clases predefinidas de Perl y de POSIX.
- \d o [[:digit:]] (Números. Equivale a [0-9])
- \s o [[:space:]] (caracteres de espacio en blanco (espacio, tabulador, nueva línea, etc))
- \w o [[:word:]] (Alfanuméricos. Equivale a [a-zA-Z0-9_])
- \b o [[:<:][:>:]] (Límite de palabra)
Además existe un "comodín", que coincide con cualquier otro caracter. Ya sea letra, número, o un caracter especial. Esta clase es el punto (.).
clases predefinidas negadas:
Para negar una clase predefinida,, el caracter seguido a la barra invertida debe ser una letra mayúscula.
- \W (coincide con cualquier caracter que no sea un alfanumérico)
- \D (coincide con cualquier caracter que no sea un número)
- \S (coincide con cualquier caracter que no sea un espacio en blanco)
Cuantificadores:
Son conjuntos de caracteres que multiplican el patrón que les precede. Mientras que con las clases de caracteres podemos buscar un dígito, o una letra; con los cuantificadores podemos buscar cero o más letras, al menos 7 dígitos, o entre tres y cinco letras mayúsculas.
- ? (Cerrar interrogación; coincide con cero o una ocurrencia del patrón. Dicho de otra forma, hace que el patrón sea opcional)
- + (más; coincide con una o más ocurrencias del patrón)
- * (Asterisco; coincide con cero, una o más ocurrencias del patrón)
- {x} (coincide con exactamente x ocurrencias del patrón)
- {x,y} (coincide con al menos x veces, y no más de y veces)
- {x,} (coincide con al menos x veces o más)
Otros metacaracteres:
- \K (Reinicia la búsqueda ignorando lo que esté a la izquierda del modificador en la captura)
- ? (Cerrar interrogación además de servir como cuantificador, puede modificar el comportamiento de otro. De forma predeterminada, un cuantificador coincide con la mayor cadena posible. Cuando se le coloca un ?, se indica que se debe coincidir con la menor cadena posible)
Esto es: dada la cadena bbbbb, b+ coincide con la cadena entera, mientras que b+? coincide solamente con b.
- | (Barra vertical; permite definir opciones para el patrón: 'perro|gato' coincide con perro y con gato)
- () (Paréntesis; agrupan patrones. Sirven para que aquel fragmento de la cadena que coincida con el patrón sea capturado, o para delimitar el alcance de un cuantificador)
Ejemplos y más ejemplos:
Todo lo anterior probablemente te sonó un poco a chino mandarín, sin embargo vamos a ver algunos ejemplos con los que creo vas a poder familiarizarte más con estos patrones de búsqueda.
Los primeros serán patrones de juguete ya que lo mismo podría hacerse de otras maneras, pero la idea es ir incrementando de a poco la dificultad. Al final dejo un audio con patrones un poco más complejos.
Van a estar escritos en diferentes lenguajes ya que lo importante en este caso no son los métodos que ofrecen cada uno de ellos, sino la construcción de los patrones. ¡ánimo!
Patrones para búsqueda de nombres:
Cadena con los nombres entre los cuales buscar
'María Marta marcela mónica miriam martina Mariana Úrsula uriel diego mariano mario'
1- Patrón para capturar una lista con los nombres que comienzan con mar, y luego tienen 1 o más caracteres alfanuméricos.
'mar\w+'
Se busca en primer lugar la sílaba mar, seguida de un caracter alfanumérico. Sin embargo luego del corchete final hay un cuantificador; el más. Por lo que se busca una o mas coincidencias de estos caracteres. La lista almacenará lo siguiente; 'marcela', 'martina', 'mariano', 'mario'
2- Almacenamos una lista de nombres que finalicen con la sílaba na
'\w+na'
En primer lugar se busca una o más coincidencias de caracteres alfanuméricos, y luego la sílaba literal na. Se almacena lo siguiente: 'martina', 'Mariana'
3- Recuperamos los nombres que tengan alguna letra u
'\w*u\w*'
El patrón comienza con un metacaracter alfanumérico, el cual tiene el cuantificador asterisco. Este busca 0 o más coincidencias. Una letra u literal, y luego el mismo metacaracter del comienzo. Se almacena ['Úrsula', 'uriel']
Recordemos que las expresiones regulares por defecto distinguen entre mayúsculas y minúsculas, cada lenguaje añade las diferentes opciones para modificar este comportamiento. Por ejemplo en python, se agrega el argumento re.I;
re.findall(r'mar\w+', nombres, re.I)
Lo que ahora almacenaría 'María', 'Marta', 'marcela', 'martina', 'Mariana', 'mariano', 'mario'
Necesitamos extraer nombres que comiencen con "mar", que el caracter siguiente sea "í", "t", u "o". Y que finalice con "i" o con "o".
'mar[íti][ao]\b'
El patrón comienza con la sílaba mar.
Seguidamente con una clase que coincide con t, con i acentuada o con la letra i.
Otra clase que coincide con a, o con la letra o.
Y finalmente un metacaracter de final de palabra. Se recupera ['María', 'Marta', 'mario']
Nota: el metacaracter \b del final de la expresión, es un límite de palabra. Esto para evitar que también se recupere mariana, lo que coincidiría con la expresión, pero no es lo que se buscaba.
Verificación de validez de un correo electrónico con javascript
Conocemos que el formato habitual de un correo electrónico está formado por 2 partes; usuario y dominio, separados por un signo arroba (@).
Un nombre de usuario puede tener letras, números, guiones bajos o medios, puntos.
El dominio también está compuesto por 2 partes; el nombre del dominio, un punto literal, y la extensión.
Con esta información vamos a crear nuestro patrón de expresión regular.
/^[\w.-]+@[\w.-]+\\.[\w.-]{2,6}$/
Descripción del patrón:
Comienza con un acento circunflejo (^), el cual es un ancla que indica que lo que se busca debe estar al comienzo de la cadena.
Seguidamente hay una clase que busca coincidencias con algún caracter alfanumérico, un punto o un guión. Esta clase tiene un cuantificador más (+), que captura una o más coincidencias.
Luego hay un arroba (@) literal.
Otra clase similar a la primera, con el mismo cuantificador.
Luego un punto literal, escapado porque de lo contrario funcionaría como comodín de expresión regular.
Una clase similar a las anteriores, pero que busca entre 2 y 6 coincidencias.
Y por último, el ancla dólares ($) que indica que esta última clase debe ser lo último de la cadena.
En el caso de JavaScript los patrones se pueden crear llamando a la función constructora del objeto RegExp, o de forma literal encerrándolo entre barras. En el ejemplo utilizo esta última junto a la función test, que devuelve true o false dependiendo de si el patrón coincide con el texto capturado en el cuadro input.
Código completo
<!--Etiquetas básicas de html-->
<!doctype html>
<html lang="es">
<head><meta charset="UTF-8">
<title>ExpresionesRegulares</title>
</head>
<script>
<!--Construímos la función que obtiene el texto del cuadro input, y busca dentro del mismo los patrones de la expresión regular-->
function mail_verify() {
<!--obtenemos el objeto del cuadro input-->
var input_box = document.getElementById("mail");
<!--guardamos en la variable str el contenido ingresado en el cuadro-->
var str = input_box.value;
<!--creamos el patrón de expresión regular.-->
var pattern = /^[\w.-]+@[\w.-]+\\.[\w.-]{2,6}$/;
if (pattern.test(str)) {
alert("correo electrónico correcto");
} else {
alert("correo electrónico incorrecto");
}
input_box.value = "";
}
</script>
<body>
<label>Correo electrónico a verificar:
<input id="mail" placeholder="escribe el correo y pulsa el botón verificar" type="text" name="nombre" required>
</label>
<button onClick='mail_verify();'>Verificar</button>
</body>
</html>
Obtener el link de descarga dentro de una página web con php
En este caso vamos a buscar una url concreta que va variando dependiendo de la versión actual del instalador.
El desafío es crear un patrón que funcione independientemente del número de versión.
En el caso de las páginas web es importante basar la expresión regular en el código fuente de la misma. Como sucede en este ejemplo, si el archivo está alojado en el propio servidor, la URL final probablemente será relativa. Es decir que en lugar de comenzar con https://dominio-de-la-página. Lo hará con la ruta interna del archivo.
Patrón de búsqueda:
'/files\/\d\.x\/reaper\d{3}_x64-install\\.exe/'
Descripción del patrón:
Comienza con el texto files, y una barra literal, las cuales deben ser escapadas tanto en php como en JavaScript para evitar conflictos con las barras que encierran la expresión.
Seguidamente un metacaracter de dígito, que coincide con algún número del 0 al 9, seguido de un punto literal también escapado.
La letra x literal, otra barra escapada, y la palabra reaper también literal.
Seguidamente otro metacaracter de dígitos, el cual tiene un cuantificador específico. Es decir que se buscan 3 números del 0 al 9.
Mas texto literal y otro punto escapado.
Código completo
<?php
// Guardamos todo el contenido del html en la variable content
$content = file_get_contents("https://reaper.fm/download.php");
// Creamos el patrón de búsqueda
$pattern = '/files\/\d.x\/reaper\d{3}_x64-install\.exe/';
// Utilizamos la función preg_match para obtener el link a través de un patrón de expresión regular, que va a crear un array con el resultado.
preg_match($pattern, $content, $download_link);
print('https://reaper.fm/' . $download_link[0]);
?>
Verificar la url de un link de youtube con AutoHotkey
Necesitamos verificar si un link se corresponde con la estructura típica de la url de un canal de youtube, por ejemplo:
https://www.youtube.com/channel/ID-del-canal
Sin embargo esta url puede comenzar tanto con http, como con https. Así como con el "www.", como sin él. En base a esto construiremos el patrón:
"^https?://(www\.)?youtube\.com/channel/[\w.-]+$"
Análisis de la expresión regular:
Comenzamos con el ancla acento circunflejo (^), el cual busca solo al comienzo de la cadena.
Seguidamente el texto literal "https?://". Aunque la letra s tiene el cuantificador cerrar interrogación (?). Lo que hace que la s sea opcional. Va a capturar tanto http, como https.
Luego hay un grupo definido con los paréntesis. El texto que figura dentro se busca exactamente como está. Después del paréntesis de cierre hay otro cuantificador cerrar interrogación (?). Lo que hace que el "www.", sea opcional.
Seguidamente el texto literal "youtube.com/channel/", con el punto escapado.
Una clase de caracteres que busca alfanuméricos,un punto, o un guión. Esta clase tiene el cuantificador más (+), por lo que busca una o más coincidencias.
Por último, el ancla dólares ($), que indica que esto último debe estar al final de la cadena.
Código completo
; Creamos un cuadro de entrada que va a almacenar lo ingresado en la variable url
InputBox, url, ingresa la url a verificar
; creamos el patrón de búsqueda
pattern := "^https?://(www.)?youtube.com/channel/[\w.-]+$"
; Creamos un condicional con la función RegExMatch que contendrá el patrón de búsqueda.
if (RegExMatch(url, pattern))
MsgBox, 0, El link ingresado tiene el formato correcto
else
MsgBox, 0, El link ingresado no tiene el formato correcto
ExitApp
Audio con ejemplos avanzados
En el extenso siguiente audio analizaremos otras expresiones regulares un poco más avanzadas, con las que recuperaremos textos agrupados, y búsquedas anticipadas.
1- raspado web de la URL de descarga de NVDA con agrupación nombrada
2- minuto 10:20. Raspado web con extracción de todos los links de la página con sus textos
3- minuto 18:45. Recuperación de una fecha con formato día, mes, año
4- Minuto 26:58. Verificación de contraseñas con búsquedas anticipadas positivas