Introduccion a las expresiones regulares

¿Qué son las expresiones regulares?

Prácticamente todos los lenguajes de programación manejan el concepto de expresiones regulares. Las expresiones regulares son una forma de buscar "patrones" dentro de una cadena de texto. Por ejemplo, nos facilitan saber si una cadena tiene el formato de fecha estilo "dd/mm/yyyy", o si una cadena tiene un formato de email válido estilo "nombre@dominio.com", etc, etc.

En casi todos los lenguajes de programación las expresiones regulares son muy similares (usan sintaxis muy similares), así que vamos aquí a verlas en general, sin concretar el lenguaje de programación, sólo para ver los conceptos. Una vez conocidos, habría que ir al detalle del lenguaje de programación concreto que se usa. Por ello, puedes seguir leyendo si quieres saber cómo funcionan las expresiones regulares, pero debes buscar un tutorial de tu lenguaje de programación si ya sabes cómo funcionan las expresiones regulares y quieres usarlas en tu lenguaje de programación.

Cuando queremos comparar una expresión regular con una cadena de texto, podemos querer dos posibles cosas:

  • Contiene una parte de la cadena de texto que cumpla esa expresión regular. Por ejemplo, podemos querer buscar una fecha en formato "dd/mm/yyyy" en la cadena "Hoy es 12/02/2017" y efectivamente, la cadena contiene una fecha en ese formato.
  • La cadena Coincide totalmente con la expresión regular. En el ejemplo anterior, la cadena "Hoy es 12/02/2017" sí contiene una fecha en formato "dd/mm/yyyy" dentro, pero NO coincide con ese formato, puesto que le sobra el trozo de texto "Hoy es " para tener la coincidencia exacta con una fecha.

Por ello, al leer a continuación, hay que fijarse si se dice contiene o coincide, porque no es exactamente lo mismo.

Expresiones simples

Vamos con el caso más sencillo. Una expresión regular sencilla es simplemente unos caracteres normales, exactamente igual que un String. Por ejemplo, si la expresión regular es "Hola" y la cadena es "Hola Mundo" :

"Hola Mundo" contiene la expresión regular "Hola"
"Hola Mundo" no coincide con la expresión regular "Hola"

Caracteres especiales

Hay algunos caracteres que tienen un sentido especial en las expresiones regulares, como por ejemplo . (punto), * (asterisco), $ (dolar), ^ (circunflejo), etc, etc. Si queremos ver si nuestra cadena contiene alguno de estos caracteres, habitualmente hay que "escaparlos" anteponiendo un \ delante (depende de tu lenguaje de programación). Por ejemplo, si queremos saber si nuestra cadena contiene "1.5", la expresión regular debería ser "1\.5", es decir, una \ delante del carácter especial punto.

"1.5 hijos" contiene la expresión regular "1\.5"

Mencionamos aquí concretamente el caracter punto ".". Equivale a cualquier caracter. Por ejemplo

"abcd" contiene "."   (punto es cualquier carácter, así que la "a" de "abcd" sería la primera coincidencia.

También son útiles el carácter "$" y el carácter "^". El primero representa final de línea y el segundo principio de línea. Por ejemplo

"Hola Mundo" contiene "Hola"
"Hola Mundo" no coincide ni contiene "^Hola$"  (La cadena debería ser sólo "Hola")
"Hola" coincide con "^Hola$"          (La cadena empieza y acaba con "Hola")

Conjunto opcional de caracteres

A veces nos interesa saber si hay alguno de un conjunto de caracteres dentro de una cadena. Por ejemplo, queremos saber si la cadena contiene "hola", pero nos da igual que la hache "h" sea mayúscula o minúscula. Para poner esto en una expresión regular, ponemos el conjunto de caracteres que nos interesan entre corchetes. La expresión regular para saber si hay un "hola" sin importar si la hache es mayúscula o minúscula, sería "[Hh]ola"

"Hola" coincide con "[Hh]ola"
"hola mundo" contiene "[Hh]ola"

Vemos aquí que los corchetes [] son caracteres especiales también, que tienen un significado para la expresión regular, que es "encerrar" posibles caracteres de interés. Si quisiéramos ver si una cadena tiene corchetes, deberíamos "escaparlos" en la expresión regular con una \ delante

"[uno]" contiene "\]"

Podemos poner "rangos" en esos conjuntos de caracteres. Por ejemplo, si queremos saber si hay una cifra, no necesitamos poner "[0123456789]" en la expresión regular, podemos poner "[0-9]", o si nos interesan las letras minúsculas, no hacer falta ponerlas todas, vale con "[a-z]", o incluso dos rangos juntos, como "[a-zA-Z]" para minúsculas y mayúsculas.

"PEDRO" contiene "[A-Z]"
"3 tigres" contiene "[0-9]"

También podemos querer lo contrario, es decir, un conjunto de caracteres que NO estén. Por ejemplo, si no queremos que haya cifras, podemos poner "[^0123456789]", o mejor, "[^0-9]". Es decir, anteponemos un ^ para decir que NO queremos esos caracteres

"Pedro" cumple con "[^0-9]" porque NO tiene cifras.

Conjuntos habituales de caracteres

Entre los conjuntos opcionales que acabamos de ver, algunos suelen repetirse mucho, como el rango de letras, o de números, o el conjunto de "espacios" que incluya todo tipo de posibles separadores de palabras como espacios, tabuladores, etc. Habitualmente suele haber expresiones reducidas para estos conjuntos habituales. Algunas de ellas

\w para letras, equivalente a [a-zA-Z]
\W para no letras, equivalente a [^a-zA-Z]
\d para dígitos, equivalente a [0-9]
\D para no dígitos, equivalente a [^0-9]
\s para espacios en blanco (espacios, tabuladores, etc).
\S para no espacios en blanco.

Estos son solo los más habituales, hay más que puedes mirar en tu lenguaje de programación favorito : Metacaracteres en Javascript, Clases de caracteres predefinidos en java, ....

"hola" contiene "\w", pero no "\d" ni "\s"  (letras, pero no digitos ni espacios)
"3 tigres" contiene "\d", "\w" y "\s"  (digitos, letras y espacios)

Repetición de caracteres

A veces nos interesa saber si en la cadena se repite un número de veces la expresión que estamos buscando, por ejemplo, saber si hay dos cifras seguidas, o cinco letras seguidas, o lo que sea. Para poder poner un rango de veces que se debe repatir algo, se pone ese rango entre llaves. Por ejemplo "a{3,5}" quiere decir una "a" entre 3 y cinco veces.

"aa" no cotiene "a{3,5}", no hay al menos tres a seguidas.
"aabaa" no cotiene "a{3,5}", no hay al menos tres a seguidas.
"aaa" sí contiene "a{3,5}"
"aaaaaaaaaaaaaaaaa" sí contiene "a{3,5}"   (hay al menos 3 a seguidas).

En el último caso, si hay entre 3 y 5 a seguidas (hay más), pero si "extraemos" la cadena que cumple la expresión regular, nuestro lenguaje de programación nos devolverá cinco "a", no toda la cadena.

"aaaaaaaaaaaaaaaaa" no coincide con "a{3,5}"   (no hay entre 3 y 5 a, hay más).

Si queremos un número exacto de veces, no ponemos rango, sino sólo el número, por ejemplo "a{5}" son exactamente 5 "a".

Si no queremos limitar el rango por uno de los lados, basta con no poner el número, por ejemplo "a{,5}" es entre 0 y 5 veces, mientras que "a{3,}" es 3 o más veces.

Hay rangos habituales, como "{0,}" (0 o más veces), "{1,}" (una o más veces}, "{0,1}" (una o ninguna). Hay caracteres especiales para estos rangos

* equivale a 0 o más veces {0,}
+ equivale a 1 o más veces {1,}
? equivale a 0 ó 1 vez {0,1}

Así, por ejemplo

"1234" coincide con "\d+"  (una o más dígitos consecutivos)
"abc" contiene "\d*"     (cero o más dígitos consecutivos)
"abc" y "abc1" contienen "\d?"  (uno o ningún dítigo)

El asterisco y el signo más intentan obtener el número máximo de caracteres posibles. Por ejemplo

"a1234bc" contiene "\d+"     (\d+ coge todos los números consecutivos posibles, es decir 1234)
"a1234bc" contiene "a.*"     (una a, seguida de cualquier carácter 0 o más veces. El ".*" coge "1234bc", es decir, todo menos la "a")

Si queremos que coja el mínimo número posible de caracteres, hay que poner un interrogante justo detrás.

"a1234bc" contiene "\d+?"     (\d+? coge sólo un dígito, el primero que encuentra, es decir "1")

Si nos interesa una secuencia de letras que se repita varias veces, se pone la secuencia entre paréntesis y detrás el número de veces que nos interesa que se repita. Por ejemplo

"papa" coincide con "(pa){2}"   ("papa" contiene dos veces "pa")

Extraer partes de la cadena

En cualquier lenguaje de programación podemos extraer el trozo de cadena que nos interese y que cumpla parte de la expresión regular. Para ello, debemos poner entre paréntesis el trozo de expresión regular que nos interesa. Por ejemplo, si tenemos una fecha en formato "dd/mm/yyyy" la expresión regular puede ser así

"11/03/2017" coincide con "\d{2}/\d{2}/\d{4}" ("\d{2}" son dos dígitos, "\d{4}" son cuatro, y los separadores /)

Si nos interesa el mes, metemos entre paréntesis los dos dígitos del mes

"11/03/2017" coincide con "\d{2}/(\d{2})/\d{4}" ("\d{2}" del mes va entre paréntesis)

Ahora, dependiendo del lenguaje de programación concreto que tengamos, habrá métodos o funciones que nos permitan extraer lo que va entre paréntesis.

Si nos interesan varias partes de la cadena, sólo tenemos que poner varios conjuntos de paréntesis. Por ejemplo, si queremos el día, el mes y el año, pondríamos

"11/03/2017" coincide con "(\d{2})/(\d{2})/(\d{4})" (Los grupos "\d{2}" y "\d{4}" van entre paréntesis)

El lenguaje de programación nos permitirá habitualmente obtener estos grupos, normalmente numerados como grupo 1, grupo 2, y grupo 3, siendo el grupo 1 el del primer paréntesis que se abre, el grupo 2 el del segundo, etc.

Utilizar lo ya encontrado más adelante en la expresión

Imagina que queremos palabras que empiecen y acaben con la misma letra, pero no nos importa cual. Si ponemos entre paréntesis la primera letra, como acabamos de ver, su grupo será el 1. Podemos usar en la expresión regular más adelante "\1" para que algo coincida con el grupo 1. Por ejemplo

"anaconda" coincide con "(\w)\w*\1"

Explicamos la expresión regular. Hemos visto que "\w" es una letra y la metemos entre paréntesis. A partir de aquí "\1" será esa primera letra. Luego "\w*" son cero o más letras. Y finalmente "\1" es lo que se haya encontrado en la primera letra. Más ejemplos

"anacondo" No coincide con "(\w)\w*\1"  (la "o" del final no coincide con la "a" del principio"
"bb"  coincide con "(\w)\w*\1"   (La primera y última letra son iguales, en medio puede haber 0 o más letras).

Ignorar lo encontrado

Vimos antes que podemos poner entre paréntesis si queremos que una secuencia concreta se repita n veces, como por ejemplo

"papa" coincide con "(pa){2}"   ("papa" contiene dos veces "pa")

Pues bien, estos paréntesis pueden entrar en conflicto con los paréntesis de búsqueda para extraer trozos de la cadena. Si no queremos que estos paréntesis formen parte de los grupos que encontramos, metemos un "?:" dentro del paréntesis, así

"papa guapo" coincide con "(?:pa){2} (\w+)"

Explicamos la expresión. "pa" dos veces, ignorándolo a la hora de extraer tozos de cadena. Luego un espacio y luego extraemos todas las letras que encontremos (una o más). Como hemos puesto "?:" dentro del primer paréntesis, la cadena que se extraiga como grupo 1 será "guapo" y no "pa".

Posición de la expresión

A veces nos interesa que el trozo de cadena que estamos buscando ocupe una posición concreta dentro de la cadena global, por ejemplo, al principio, al final, detrás de otra cadena concreta, etc. Hay forma de indicar dónde queremos encontrar la cadena dentro de la cadena principal, usando expresiones específicas para ello. Ya vimos dos de ellas

^ representa principio de cadena
$ representa final de cadena

Por ejemplo

"Hola mundo" contiene "*Hola"    (la cadena empieza por "Hola")
"Hola mundo" no contiene "Hola$"  (la cadena no termina en "Hola")

pero tenemos más

\b representa un principio/fin de palabra
\B reprsenta lo contrario, un no principio/fin de palabra

por ejemplo

"Hola mundo" contiene "\bHola\b"    ("Hola" es una palabra separada de otras letras por espacios, inicio de cadena, fin de cadena...)
"HolaHola no contiene "\bHola\b"    ("Hola" forma parte de una palbra mas grande "HolaHola")
"HolaHola contiene "\bHola\B"    ("HolaHoa" empieza con "Hola" pero no termina ahí la palabra, sino que sigue)

y podemos poner lo que queramos con

($=expresion)    la posición que cumpla esta expresión
($!expresion)    la posición que NO cumpla con esta expresión

Por ejemplo

"11 km" cumple con "\d+(?= km)"   (\d+ es una o más cifras delante de " km" 
"11 m y km" no cumple ni contiene "\d+(?= km)"    (no hay una o más cifras justo delante de " km"