Buscar este blog

sábado, 15 de agosto de 2020

Expresiones regulares (Regexp)

Sirven para buscar cadenas de caracteres dentro de un texto. Son muy potentes en las búsquedas y eso las hace complejas cuando las búsquedas también lo son.

Hay quien dice que “Si para solucionar un problema necesitas una expresión regular, ahora tienes 2 problemas”.

Dependiendo del programa que las utilice, pueden variar un poco, aunque en general casi todos los programas coinciden en la mayoría de las cosas. Se pueden utilizar desde la línea de órdenes de GNU/Linux con el comando grep o desde muchos editores de textos (los más profesionales) y lenguajes de programación. Los lenguajes de programación utilizan librerías de funciones para trabajar con expresiones regulares. Una de las librerías más conocidas y utilizada es PCRE, una librería libre implementada en C e inspirada en el interfaz externo de Perl. Como las expreg. pueden llegar a ser muy complejas, es importante comentarlas desgranando sus partes.

 

El procesador de textos Writer de LibreOffice también las tiene como opción en las búsquedas.

Básicamente, una expresión regular es un conjunto de caracteres, una plantilla, que utilizamos para buscar coincidencias dentro de un texto. Si pensamos en los sistemas operativos, las expresiones regulares son muchísimo mas potentes que los típicos comodines ‘*’ y ‘?’ utilizados a la hora de buscar ficheros.

En una expresión regular (expreg.), para indicar lo que queremos buscar utilizamos una cadena de caracteres que puede estar compuesta de letras, número y signos. Aquí hay que tener en cuenta que muchos signos tienen un significado especial, a esos los llamamos metacaracteres.

Un ejemplo de búsqueda puede ser algo tan extraño como esto: ^Cod[0-9]{5}$

En muchas publicaciones las expreg. las representan entre //, donde las barras inclinadas no forman parte de la expresión regular, pero lo hacen así para que queden más delimitadas y más reconocibles. Aquí también usaremos este método. Ej.: /^Cod[0-9]{5}$/

Aparte de los caracteres que forman la expreg. también tenemos delimitadores y modificadores que pueden cambiar el comportamiento de una búsqueda, como por ejemplo, distinguir entre mayúsculas y minúsculas. Aquí si se usan modificadores se representarán tras la barra “/” de cierre de la expreg. Ej.: /^Cod[0-9]{5}$/gmi

Para los ejemplos aquí descritos estoy utilizando el editor de textos Notepad++, el cual utiliza delimitadores y modificadores que están como opciones en la ventana de búsqueda (hay una casilla que lo marca). Ojo que también hay una casilla que indica que en la búsqueda se utiliza una expreg.

  

Una web muy interesante para practicar las expreg. es la siguiente: https://regexr.com/

 Ahí se puede practicar y ver la lógica de la expreg. utilizada desgranando cada componente.

 

Los modificadores (expression flags)

Imagínese que quiere buscar los caracteres “aeiou” independientemente de que estén en mayúsculas o minúsculas. La forma más fácil de hacerlo es indicar al motor de búsqueda que es indiferente que se usen mayúsculas o minúsculas. Esto se consigue mediante los modificadores, los cuales no forman parte de la cadena de búsqueda.

Dependiendo del software utilizado, estos modificadores se indican de distintas formas. En Notepad++ se hace marcando casillas en la ventana de búsqueda, en otros se indican tras el limitador final de la cadena buscada (la barra “/”). Ejemplo: /aeiou/i

Los modificadores pueden ser los siguientes:

  • g” (global). Conserva el índice de la última coincidencia, permitiendo que las búsquedas posteriores comiencen desde el final de la coincidencia anterior. Por tanto, no se detiene ante la primera coincidencia.

  • "i" (case insensitive). Indica que no se distingue entre mayúsculas o minúsculas

  • "m" (multiline). Indica que el patrón se aplica a cada línea de texto, por lo que los metacaracteres de principio y final ("^" y "$") indican principio y final de cada línea. Si no está activado, todo el texto se considera como una sola cadena.

  • "s" (single line). Indica que el metacarácter punto "." reemplazar a cualquier carácter, incluyendo el salto de línea (por defecto, el punto no reemplaza al salto de línea).

  • "u" (unicode). Indica que el patrón está codificado en UTF-8

Ejemplo de búsqueda global de todas las ‘a’ al final de cada línea en un texto, línea a línea, en la que no importan si los caracteres están en mayúsculas o minúsculas: /a$/gmi

 

Buscar un texto, un literal

Una búsqueda sencilla es aquella en la que no hay ningún metacaracter, es decir, buscamos algo literal. Por ejemplo: Vamos a buscar la palabra ‘mar’ en un texto como el siguiente:

En el mar hay muchas algas.
Cuando sube la marea quedan muchas algas por la playa.

ATENCIÓN: para los ejemplos se va a escribir la expreg. entre delimitadores, //, pero recuerde que no se ponen en Notepad++, lo usamos para que quede a simple vista claramente delimitada la expresión regular.

La expeg. en Notepad++ sería poner en la casilla “Buscar” lo siguiente (sin delimitadores): mar

 

Si pulsamos sobre el botón “Contar” se muestran en la parte baja de la ventana de búsqueda el número de coincidencias encontradas. El resultado serían 2 coincidencias (aquí las vamos a destacar MARCÁNDOLAS EN AMARILLO):

En el mar hay muchas algas.

Cuando sube la marea quedan muchas algas por la playa.

Bueno, tal vez te haya llevado una sorpresa. Imagine que su objetivo era cambiar la palabra mar por río. El resultado en la segunda frase sería algo tan feo como:

En el río hay muchas algas.

Cuando sube la rioea quedan muchas algas por la playa.

Esto nos puede servir como lección a la hora de aplicar a un texto muy grande una expreg. ya que nos podemos llevar sorpresas, por tanto hay que usarlas con precaución y asegurándose de que el resultado es el esperado. A las expreg. las carga el diablo.


Metacaracteres

Son caracteres que tienen un significado especial, es decir, de mano no representan un literal. Por ejemplo, el asterisco ‘*’ no representa a un asterisco, indica 0 o más caracteres iguales al anterior.

¿Qué pasa si quiero usar un asterisco en una búsqueda? Pues entonces, lo hay que “escapar” usando otro metacaracter, la barra inclinada a izquierda ‘\’. Por ejemplo, buscar en un texto todos los asteriscos que haya: \*

Estos son los metacaracteres que pueden aparecer en una expreg. y que por tanto son interpretados de forma diferente:

  • Raya invertida (Backslash \)

  • Símbolo de Dólar ($)

  • Punto (.)

  • Asterisco (*)

  • Más (+)

  • Interrogación (?)

  • Tubería (Pipe | )

  • Los paréntesis ( )

  • Los paréntesis cuadrados (bracket [ ] ). En realidad solo el de apertura.

  • Las llaves (curly brace {} ). En realidad solo el de apertura y si le sigue un número.

  • Acento circunflejo (Caret


RECUERDE: si busca uno de estos caracteres lo tiene que "escapar" con la barra "\".


Secuencias de escape

Hay caracteres no tienen representación gráfica directa o que se distinga claramente, por ejemplo, el carácter "tabulador" o el espacio. Existe una forma de representación más clara para estos caracteres usando el metacaracter de escape "\":

  • \s Espacio (ascii 32, código hexadecimal 20)

  • \t Tabulador (acii 9, código hexadecimal 09)

  • \n Nueva línea (ascii 10, código hexadecimal 0A)

  • \r Retorno de carro (ascii 13, código hexadecimal 0D). En Windows se utiliza para fin de línea la combinación \r\n

  • \e Escape (ascii 27, código hexadecimal 1B)

  • \xhh Carácter con código hexadecimal "hh" (por ejemplo \x1b es el carácter Escape)

  • \N{U+hh} Carácter con código Unicode "hh" (por ejemplo \N{U+263a} (hexadecimal) es el carácter Unicode cara blanca sonriente”)


Un carácter cualquiera (.) excepto salto de línea

El metacaracter ‘.’ sustituye a un carácter cualquiera, excepto a un salto de línea (\n).

Ejemplo: Buscamos cadenas donde haya al menos 3 caracteres en el siguiente orden: una ‘m’, el siguiente carácter no importa y el tercero es un 1. La expreg. sería la siguiente: /m.1/

En el texto: mx1 mari1 ma1kl sem1 semo1 asdmR1fe

Aparecen 4 coincidencias.


Un carácter o ninguno como el anterior (?)

El metacaracter ‘?’ sustituye a 0 o 1 carácter como el que tiene a su izquierda. Queremos cambiar la palabra ‘foto’ o ‘fotos’ por la palabra ‘imagen’. En Notepad++ iríamos al menú “Search → Replace...”.

La expreg. a utilizar: /fotos?/

Donde ‘?’ sustituye a 0 o a 1 carácter ‘s’.

fotoCASA fotosCASA12 fotosCASA31 1fotosCASA

El resultado sería:

imagenCASA imagenCASA12 imagenCASA31 1imagenCASA

Ojo con estos metacaracteres que permiten 0 ocurrencias, hay veces que dan sorpresas.


Un carácter o varios como el anterior (+)

El metacaracter ‘+’ sustituye a 1 o a varios caracteres como el que tiene a su izquierda. Buscamos cadenas en que haya una o varias “m” seguidas. La expreg. sería: /m+/

En el texto: mar eltommmmi hola mmmmmiero barto abcmmmmm abmzz

Aparecen 5 coincidencias. El metacaracter ‘+’ indica que el carácter anterior (la ‘m’ en nuestro ejemplo) debe aparecer 1 o más veces. AL MENOS UNA.


Cero o varios como el anterior (*)

El metacaracter ‘*’ sustituye a 0 o a varios caracteres como el que tiene a su izquierda. Si la búsqueda fuese: /m*/ y sustituir lo que se encuentra por “AZ”, en el texto:

El mar está en calma muchos días.

El resultado sería: AZEAZlAZ AZaAZrAZ AZeAZsAZtAZáAZ AZeAZnAZ AZcAZaAZlAZaAZ.AZ

¿Por qué? Buscamos apariciones de 0 o más ‘m’. Vamos a analizar la palabra ‘mar’ que está marcada en amarillo en el resultado. Se cambia ‘m’ por ‘AZ’. Después viene la ‘a’ que tiene una coincidencia de longitud 0 (recuerde 0 o más ‘m’), por lo que se deja la ‘a’ y se añade ‘AZ’. Después viene la ‘r’ que tiene una coincidencia de longitud 0 (recuerde 0 o más ‘m’), por lo que se deja la ‘r’ y se añade ‘AZ’. ¡Un poco locura! Así que cuidado con el ‘*’.

Si la búsqueda fuese: /m+/

El resultado sería mejor: El AZar está en calAZa AZuchos días.

Repito, hay que tener mucho cuidado con los metacaracteres que permiten 0 ocurrencias, los resultados pueden ser sorprendentes.

En el siguiente texto, quiero quitar los ‘0’ y cambiar la palabra ‘imagen’ por ‘foto’:


 

imagen.jpg imagen001.jpg imagen002.jpg imagen003.jpg

La expreg. sería: /imagen0*/

El resultado sería: foto.jpg foto1.jpg foto2.jpg foto3.jpg


Limitar las coincidencias (+?) (*?)

Tanto el ‘*’ como el ‘+’ están pensados para conseguir coincidencias grandes (voracidad) ya que ambos representa ‘muchos caracteres’. Con la combinación de ‘.*’ o ‘.+’ es fácil encontrarse con coincidencias más grandes de lo esperado. Por ejemplo la expreg. siguiente con las comillas incluidas : /“.+”/

En el texto: Mi avatar es "Esbardu", el tuyo "Caparina" y el de él “Jabato”.

Tendríamos una sola coincidencia muy larga (marcada de amarillo). Ésta búsqueda la podemos traducir por: busca unas comillas y a partir de ahí lo coges todo hasta encontrar las últimas comillas.

Si la intención era obtener solo las palabras que están entre comillas, lo podremos conseguir con un limitador de coincidencias. Usaremos el metacaracter ya conocido ‘?’ detrás del metacaracter ‘*’ o ‘+’; dejando la expreg. de la siguiente forma: /“.+?”/

Las coincidencias ahora serían 3:

Mi avatar es "Esbardu", el tuyo "Caparina" y el de él “Jabato”.


Un número exacto de caracteres { }

Si queremos buscar un número exacto de un carácter cualquiera, se usa el par de metacaracter ‘{}’ indicando en el medio el número mínimo, máximo o un intervalo.

Buscar las cadenas donde aparecen al menos 3 caracteres ‘a’ seguidos. La expreg. sería: /a{3}/

Aparecen las siguientes coincidencias: mooaaaaa m3ooaaaXXXa

Mínimo 4: /a{4,}/

Aparecen las siguientes coincidencias: mooaaa aaaa m3ooaaaXXXaaaaaaaZZ

Máximo 4, al menos uno: /a{1,4}/

Aparecen las siguientes coincidencias: mooa aaaa ooaaaXaaaaaaaaZZ

 

Caracteres alfanuméricos y subrayado (\w) (\W)

El metacaracter ‘\w’ equivale solo a cualquier carácter alfanumérico y subrayado. Dependiendo del programa, algunos no dan por válido la ‘ñ’ o los acentos.

El espacio y los signos de puntuación no se encuentran. La expreg.: /\w/

En: la_suma x+3=25

Aparecen las siguientes coincidencias: la_suma, x+3=25

Si queremos justo lo contrario usamos: /\W/

Aparecen las siguientes coincidencias: la_suma, x+3=25


El espacio (\s) (\S)

El metacaracter “\s” se interpreta como un espacio. Esto es útil porque al escribir una expreg. es difícil ver cuantos espacios hay seguidos. En los editores o procesadores de texto se podría poner el espacio directamente, pero así queda más claro.

La expreg.: /mar\s/

En el mar hay muchas algas cuando baja la marea.

Aparece 1 coincidencia. Estamos buscando los caracteres: m-a-r-’espacio’.

Imagine que el texto fuese: Me gusta caminar por el mar. Hay un maremoto. El mar, ¡que bonito!

En este caso no encontraría ninguna coincidencia.

Si queremos saber cuantos espacios hay en un texto: /\s/

El metacaracter “\S” selecciona todo lo que no es un espacio. Si queremos saber cuantos caracteres diferentes de espacio hay en un texto usamos: /\S/


Límites de una cadena (\b) (\B)

El metacaracter “\b” (word boundary) indica final o inicio de una palabra. Caracteres como el punto, la coma, el espacio, etc. se consideran separadores de palabras. La barra baja (subrayado) no. Por tanto, ayuda a trabajar con palabras.

Si en un texto quiero encontrar la palabra “mar”, la expreg.: /mar\b/i

Usamos el modificador “i” para indicar que no importan mayúsculas o minúsculas.

En el mar hay muchas algas.

Mar, que bonita eres. Me gusta remar en mi canoa.

Me gusta caminar por el mar.

Vemos que el resultado no es tan bueno como esperábamos. La búsqueda la podemos traducir como: buscar m-a-r y el siguiente carácter tiene que ser un final de cadena (el cual no se selecciona).

Podemos mejorar la búsqueda con: /\bmar\b/i

En el texto:
En el mar hay muchas algas.
Mar, que bonita eres. Me gusta remar en mi canoa.
Me gusta caminar por el
mar.

Aparecen 3 coincidencias.

El metacaracter “\B” hace justo lo contrario, equivale a cualquier carácter que sea una letra, número o subrayado (barra baja). Ej.: /mar\B/

Remaria en mi canoa.
Me gusta caminar por el mar.
La mar5 sube. El mar está azul. Repito marmar.

Encontraría 3 coincidencias.

La expreg.: \Bmar

R_maria en mi canoa.
La mar5 sube. El mar está azul. Repito mar
mar.

Encontraría 2 coincidencias.

IMPORTANTE: Hay que tener en cuenta que la cadena se reinicia cada vez que se encuentra una ocurrencia.

La expreg.:

La marea sube. El mar está azul. Quiero amar. Repito marmar.

Aparecen 4 coincidencias. Sorprende la última ocurrencia, pero es que se reinicia la búsqueda tras cada ocurrencia encontrada.

 

Que empiece o que termine (\<) (\>)

Los metacaracteres “\<” y “\>” sirven para marcar el comienzo y el fin de una palabra, por tanto son muy parecidos a “\b”. Hay que tener en cuenta que la cadena se reinicia cada vez que se encuentra una ocurrencia.

La regexp: /\<mar/

Al remar me canso. El marisco está fresco. Me gusta el mar.

Aparecen 2 coincidencias. Con: /\<mar\>/ solo aparece una: Me gusta el mar.


Inicio de línea (^)

El signo ‘^’ indica que hay que buscar algo que está al comienzo de una linea. En el siguiente texto, queremos encontrar cuantas veces aparece las letras ‘ola’ al principio de una línea.

Si escribimos: /^ola/i

La ola del mar.

Ola que viene y va.

Olas grandes.

Encuentra 2 coincidencias.



Fin de línea ($)

El signo ‘$’ indica que hay que buscar algo que está al final de una linea. Por ejemplo, tenemos un listado de ficheros y solo quiero ver los de extensión ‘jpg’. Escribimos: /jpg$/

fich.txt

fich.jpg

fich2.txt

fijpg

Encuentra 2 coincidencias. Pero igual no es eso lo que queríamos, ya que aparece un fichero sin extensión cuyo nombre termina en ‘jpg’.

Si escribimos: /.jpg$/

Tampoco nos vale ¿Por qué? Porque el ‘.’ es un signo especial, un metacaracter, dentro de las expreg. Para que no lo tome como especial, lo hay que “escapar” usando el signo ‘\’. Quedaría así: /\.jpg$/

Ahora sí solo encuentra 1 coincidencia: fich.jpg



Un dígito (\d) (\D)

Para seleccionar un dígito usamos el metacaracter “\d”. Imagine que tenemos un texto con códigos de 3 clases de productos: mesa (mes), silla (sil) y puerta (pue). Y dentro de cada clase de producto distintos modelos referenciados por 2 o 4 dígitos. Por ejemplo, tenemos el siguiente listado con los productos vendidos:

mes4567 pue19 sil53 mes82 mes12 sil51 sil12 sil2585

Quiero saber cuantas mesas (mes) se han vendido. La expreg. sería simple, buscar el literal: /mes/

El resultado son 3 coincidencias: mes4567 pue19 sil53 mes82 mes12 sil51 sil12 sil2585

Ahora quiero saber cuantas mesas con código de 4 dígitos se han vendido: /mes\d\d\d\d/

mes4567 pue19 sil53 mes82 mes12 sil51 sil12 sil2585

Solo una mesa con código de 4 dígitos. Vemos que el signo ‘\d’ sustituye a un dígito (de 0 a 9).

Cuidado ahora, quiero saber cuantas mesas con código de 2 dígitos se han vendido: /mes\d\d\/

mes4567 pue19 sil53 mes82 mes12 sil51 sil12 sil2585

ERROR: aparecen 3 coincidencias, cuando tenían que ser solo 2. El problema está en que buscamos un patrón donde está el literal “mes” seguido de 2 dígitos. Y claro, ‘mes4567’, lo cumple.

Para asegurarme de que solo aparezcan mesas con 2 dígitos uso: /mes\d\d\b/

mes4567 pue19 sil53 mes82 mes12 sil51 sil12 sil2585.

Recuerde que el ‘\b’ marca un inicio o final de cadena.

Para seleccionar algo que NO sea un dígito usamos el metacaracter “\D”. Por ejemplo: /a\D-\d\d/

Encontraría cosas como: aB-45 a4-97 aa-23 a9-zz



Uno u Otro ( | )

En ocasiones no queremos buscar un carácter concreto, sino varios. Con el metacaracter ‘|’ indicamos que caracteres nos valen. También lo podemos aplicar a cadenas de varios caracteres.

Por ejemplo, queremos contar todas las ‘a’ y ‘u’ en un texto. La expreg. sería: /a|u/

En un lugar de la Mancha.

Aparecen 6 coincidencias. Si la expreg. fuese: /a|e|u/

En un lugar de la Mancha.

Aparecen 8 coincidencias.

Si la expreg. fuese: /mes|más/

Este mes cobraré más y compraré una mesa.

Aparecen 3 coincidencias.

 

Conjuntos de caracteres [ ]

Si hay un conjunto de caracteres independientes que nos valen de forma individual podemos usar el metacaracter ‘|’ para elegir a unos o a otros, o bien poner los caracteres válidos entre corchetes.

Si la expreg. fuese: /[aeu]/

En un lugar de la Mancha.

Aparecen 8 coincidencias. Equivale a: /a|e|u/

También podemos indicar rangos, por ejemplo los caracteres de la ‘a’ a la ‘z’: /[a-z]/

Ojo con la ‘ñ’ y los acentos que posiblemente no los tome dentro del rango.

También podemos aplicarlo a números del 0 al 9, pero en realidad los toma como caracteres, por lo que no podremos indicar de 0 a 15 por ejemplo. La sisguente expreg. no es lo que parece: /[0-35]

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

No hay que pensar en números, sino en caracteres. Selecciona el 0, 1, 2, 3 y 5.


Negando el conjunto ([^ ])

Podemos negar el conjunto de caracteres mediante el uso del acento circunflejo tras la apertura del corchete. Por ejemplo buscar cualquier carácter menos la ‘a’, la ‘e’ y la ‘u’: /[^aeu]/

En un lugar de la Mancha.

Aparecen 17 coincidencias.

 

Agrupación ( )

Con los paréntesis se logra que un grupo de caracteres sean considerados como una entidad. Hasta ahora un metacaracter afectaba al carácter anterior, de esta manera se puede hacer que afecte a un grupo de ellos.

Por ejemplo, la expreg. siguiente: /a+/

Encuentra en un texto una o varias ‘a’ juntas. En el texto:

En un lugar de la Maaaancha.

La expreg. siguiente: /(ae)+/

Encuentra parejas de ‘ae’ juntas.

ae aeae aaeeeeaa aaeeaaee

Aparecen 6 coincidencias.

 

La expreg. siguiente: /(ab)+(\d\d)+/

Encuentra parejas de una o varias ‘ab’ seguidas de parejas de 2 dígitos.

abab99 abcd 99ab23xx ababab1234567oo aba99

Aparecen 3 coincidencias.


La expreg. siguiente: /(ab)+((\d\d)|(xx))/

Encuentra parejas de una o varias ‘ab’ seguidas de una pareja de 2 dígitos o 2 ‘xx’.

abab9978 abcd 99abababxxxx8

Aparecen 2 coincidencias.


La expreg. siguiente: /(ab)+((\d\d)|(xx))*/

Encuentra parejas de una o varias ‘ab’ seguidas de parejas de 2 dígitos o ‘xx’ las cuales se pueden repetir de 0 a infinitas veces.

abab95859 abcd 99ababxxxx

Aparecen 3 coincidencias.

Las agrupaciones encontradas en una expreg. se numeran internamente (empezando por 1 y hasta 9). Ese número lo podemos usar como referencia dentro de la misma expreg. Por ejemplo, si buscamos en un texto todos los caracteres comprendidos entre la cadena ‘hola’ y su repetición ‘hola’: /(hola).*\1/

Encontraría cosas como: creaholaAEskj452holated

Sería equivalente a usar: /(hola).*(hola)/

También se puede utilizar un nombre, una etiqueta, en lugar de un número para cada agrupación. La forma de poner la etiqueta o hacer referencia a ella puede variar. Estos son algunos ejemplos:

  • Etiqueta                  Referencia
  • (?<name>...)           \k<name>
  • (?'name'...)               \k'name'
  • (?P<name>...)          (?P=name)

     

El ejemplo anterior quedaría así: /(?<saludo>hola).*\k<saludo>/

Uno hola esto esta bien hola dos


La siguiente expreg. busca cadenas de caracteres que tengan en su interior la cadena ‘abc’, ’xyz’, ’xyz’ y ‘abc’ (en ese orden) con posibles caracteres por el medio: /(abc).*(xyz).*\2.*\1/

Equivale a: /(abc).*(xyz).*(xyz).*(abc)/

Encontraría cosas como (2 coincidencias):

gskabcAaBxyz33xyzks45sabcFEedf

abcxyzxyzabc


Atención a este ejemplo. En una página web, podemos buscar lo que se encuentra entre las etiquetas “div” o “p” con la siguiente expreg.:/<(div|p)>.*<\/(\1)>/

<div>Mi página web</div>

<p>Bienvenidos</p>

<div>Nos volveremos a ver</p>

Fijarse en el detalle que se está buscando “div” o “p” pero lo que encuentre más tarde debe coincidir con lo que se encontró primero, por eso la última frase no es seleccionada.

En cambio la expreg.: /<(div|p)>.*<\/(div|p)>/

<div>Mi página web</div>

<p>Bienvenidos</p>

<div>Nos volveremos a ver</p>

Aparecen 3 coincidencias. No tienen por que coincidir la etiqueta de apertura y cierre.

 

Ojo a este ejemplo. La expreg.: /(\w)a\1/

Encuentra trozos de 3 caracteres donde el del medio es una ‘a’ y los extremos son iguales.

23a33 edad casas


También es posible indicar que una agrupación no se contabilice, no se numere. Esto es útil cuando se utilizan las expreg. en lenguajes de programación, aunque también se puede usar con los editores de texto.

En el caso de un lenguaje de programación, usando “?:” indicamos que una agrupación no aparezca en la matriz de resultados en un programa.

La expreg.: /(?:Calle|Vía|Avenida)\s.*(Avilés|Siero)/

En la matriz de resultados dentro del programa aparece:

posición[0]=Calle Asturias 24, Avilés

posición[1]=Avilés

Esto es así porque la primera agrupación que aparece no se cuenta, por tanto la segunda: (Avilés|Siero) pasaría a ser la primera agrupación, la numerada con 1.


La misma expreg. en Notepad++ encontraría los siguientes resultados:

Calle Asturias 24, Avilés

Calle Asturias 24, Noreña

Vía la buena vida 25, 2ºizq, Avilés

Lugar la buena vida 25, 2ºizq, Avilés


En la expreg.: (?:Calle|Vía|Avenida)\s.*(Avilés|Siero),\s\1

Calle Asturias 24, Avilés, Avilés

Calle Asturias 24, Noreña

Avenida Asturias 24, Pola de Siero, Siero

Vía la buena vida 25, 2ºizq, Avilés, Siero

Fijarse que el grupo “\1” es “(Avilés|Siero)” ya que el primer grupo lo excluimos (?:Calle|Vía|Avenida).


Aserciones

Permiten indicar que hay ciertos caracteres que han de estar presentes, o no, para que lo que busquemos sea una coincidencia. Lo novedoso es que esos caracteres NO forman parte de la cadena buscada, pero han de estar. Estos caracteres que deben existir pueden estar antes o después de la cadena realmente buscada.

Imagine que busco la palabra ‘balas’ solo si antes están los caracteres ‘anti’. Con lo cual una coincidencia válida sería: antibalas. Una no válida sería: resbalas.

Estas aserciones son conocidas como "mirar alrededor" (look around) y se podrían entender como diferentes formas de ver si un patrón está (o no está) precedido o sucedido por otro patrón. Se indican encerradas entre paréntesis y con el carácter "?". Dependiendo de que estén antes o después de la cadena buscada y de la obligación de que se cumplan o no, da como resultado 4 tipos de aserciones.

  • Si acaba en (Lookahead): Hay coincidencia en la búsqueda si están los caracteres indicados en la aserción justo tras la cadena buscada.
    La expreg. siguiente: /guarda(?=bosques|espaldas)/
    Lo podemos leer como: busca ‘guarda’ pero solo si va seguido de ‘bosques’ o ‘espaldas’
    El guardabosques rompió el guardabarros de la bici del guardaespaldas.

  • Si no acaba en (Negative lookahead): Hay coincidencia en la búsqueda si NO están los caracteres indicados en la aserción justo tras la cadena buscada.
    La expreg. siguiente: /guarda(?!bosques|espaldas)/

    El guardabosques rompió el guardabarros de la bici del guardaespaldas.

  • Si empieza por (Lookbehind assertion): Hay coincidencia en la búsqueda si están los caracteres indicados en la aserción justo antes de la cadena buscada.
    La expreg. siguiente: /(?
    <=moto)cicleta/
    Lo podemos leer como: busca ‘cicleta’ pero solo si va precedido de ‘moto’.
    Se me estropeó el motor de la motocicleta al ir a buscar la lancha motora.

  • Si no empieza por (Negative lookbehind): Hay coincidencia en la búsqueda si NO están los caracteres indicados en la aserción antes de la cadena buscada.
    La expreg. siguiente: /(?
    <!moto)cicleta/
    Se me estropeó la bicicleta, no la motocicleta.

Hay que tener en cuenta que en una misma expreg. puede haber varias aserciones juntas. Por ejemplo: Buscar la cadena ‘123’ siempre que venga precedida de ‘casa’ y seguida de‘verano’.

La expreg.: /(?<=casa)123(?=verano)/

Encontraría cosas como: 234casa123veranokj34ldf adios123hola

También podemos hacer búsquedas que aparentemente rompen el orden de colocación de las aserciones. Podríamos decir que son trucos que nos ayudan a ciertas búsquedas. Por ejemplo, imagine que tenemos un listado de códigos de 5 cifras (1 por línea) y buscamos aquellos que contengan un ‘33’.

La expreg.: (?=.*33)(\d{5})

asb33

38923

33823

12733

31334

Hemos empezado por “acaba en”, como en realidad antes no hay nada, busca (?=.*33), es decir, busca una cadena que tenga un 33. Si no encuentra nada el resultado de la búsqueda es negativo y no se seleccionaría nada. Si encontró algo, a continuación se buscan 5 dígitos desde el inicio de línea (no desde el 33). Como los códigos son de 5 caracteres y están uno por línea, encontrará todos los códigos numéricos que tengan un 33.

Ojo con esta búsqueda. Si en una linea hay algo como lo siguiente, el resultado no sería el esperado:

12345asb33hk

¿Por qué? La respuesta es que en una primera pasada encuentra en la cadena un 33. A continuación busca 5 dígitos desde el inicio y los encuentra pero no incluyendo al 33.

 

La expreg.: /(?=33)(\d{5})$/

Mostraría solo los que empiezan por 33. Primero se encuentra un 33 al comienzo y a continuación se buscan 5 dígitos desde donde comienza el 33 (incluido).

asb33

32333

33743

 

La expreg.: /^(\d{5})(?<=33)$/

Mostraría solo los que terminan en 33. Ahora usamos la aserción “si empieza por”. Primero busca una secuencia de 5 dígitos desde el inicio de línea. La posición de búsqueda queda al final del último de los 5 dígitos (si los encuentra). Si ahora, partiendo de esa posición final de 5 dígitos consecutivos encuentra un 33 justo antes (empieza por 33 antes de la posición actual), se marca como una coincidencia.

3359533333
12333336
23933

 

Agrupación atómica

Permite buscar una cadena donde se define entre paréntesis una agrupación atómica que es parte obligatoria de la cadena (subcadena) que se busca y que para que haya coincidencia debe de aparecer independientemente del resto de la cadena. Se define entre paréntesis y tras el paréntesis de apertura aparece “?>”.

La expreg.: /(?>[ab]+)\w/

Buscamos una cadena formada por letras ‘a’ o ‘b’ (al menos una). Esta es la parte obligatoria. Aparte, también buscamos que a continuación haya un carácter más (\w) pero DIFERENTE de ‘a’ y ‘b’ (ya que si no formaría parte de la agrupación atómica). En el ejemplo encuentra 4 coincidencias:

a3 bZ bbab4 bbabaa aaabbababcxxx


La expreg.: (?>[ab]+)a

No encontraría ninguna coincidencia ya que la ‘a’ forma parte de la aserción y de la parte buscada, por lo que siempre faltaría la ‘a’ final.


La expreg.: (?>[ab]{3})a

a3 bZ bbab4 bbabaa aaabbababcxxx

Encuentra 2 coincidencias donde hay un conjunto de 3 ‘a’ o ‘b’ más una ‘a’ final.



Condicionales

Se pueden usar condiciones del tipo “(?if then|else)” como en los lenguajes de programación. Se utilizan para buscar coincidencias dependiendo de que se cumpla una condición.

Si la parte “si” (if) se evalúa como verdadera, entonces se intentará encontrar coincidencia con la parte "entonces" (then). De lo contrario, se intenta buscar coincidencia en la parte “si no” (else), la cual no es obligatorio que esté presente.

Toda la expresión condicional va encerrada entre paréntesis. El de apertura debe ser seguido por un signo de interrogación, la parte de "si", la parte de "entonces". Esta última parte puede ser seguida (no obligatorio) por una barra vertical y la parte que equivale al “si no”.

Veámoslo con un ejemplo. Mi amiga Ana aparece en unos listados, pero unos veces aparece con su nombre completo, Ana María Suárez Teva, y otras veces con el nombre acortado, Ana Suárez. La expreg. a utilizar para encontrarla sería: /^Ana (Isabel )?(?(1)Suárez Teva|\Suárez)$/

Juana Suárez Ortea
Ana Isabel Suárez Teva
Ana María Ortea Blanco
Ana Pérez García
Ana Suárez
Luis Suárez Suárez

La explicación: Desde el comienzo de cada línea (^) buscamos “Ana “ y opcionalmente “(Isabel )?”. A continuación viene la condición “(?” y preguntamos por el grupo (1), es decir, preguntamos: Si se encuentra la cadena “Isabel “ entonces a continuación viene “Suárez Teva” y fin de línea ($), si no “|” se encuentra la cadena “Isabel “ a continuación viene “Suarez” y fin de línea ($).


Otro ejemplo. Tenemos un listado de códigos de los cuales me interesan los que comienzan por “F”, dos números y la palabra “mal”. También me interesan los que son dos números y la palabra “bien”.

La expreg. sería: /^(F)?\d\d(?(1)mal|bien)$/

F78mal
23bien
234bien
F456mal
F23mal
99bien

La explicación es la siguiente: Se seleccionan los códigos que comienzan por “F” y dos dígitos, y van seguidos de “mal”; si no comienzan por “F” los dos dígitos, deben ir seguidos de “bien”.

Para obtener el mismo resultado, podríamos haber usado lo siguiente: /^(F\d\dmal|\d\dbien)$/

Que viene a decir: Selecciona empezando por el principio de cada línea lo que comienza por “F”, 2 dígitos y “mal”; o selecciona lo que comienza por 2 dígitos y “bien”.

 

Ejemplos variados

  • Cuantos caracteres tiene un texto. Se cuentan el fin de línea y salto de carro.
    La expreg. sería: /./

  • Seleccionar todo: /.*/ o también: /.+/

  • En un texto histórico se buscan los años comprendidos entre 1500 y 1599.
    La expreg.: /\b15\d\d\b/

    La conquista de América fue el proceso de exploración, conquista y asentamiento en el Nuevo Mundo realizado por España en el siglo XVI, y en la que participaron otras potencias europeas posteriormente, después de que Cristóbal Colón descubriera América en 1492. En abril de 1519, Hernán Cortés tomó tierra en la costa de México con aproximadamente 450 soldados. Gracias al apoyo de los pueblos indígenas oprimidos por los mexicas, el 13 de agosto de 1521 se logró la entrada en Tenochtitlan. Fué el principio de la liberación de muchos pueblos oprimidos. Se acabaron así los sacrificios humanos en un ritual macabro en el cual se sujetada de las extremidades a las víctimas tirando hacia abajo para combar la espalda y, mediante un corte a la altura del diafragma, se les extirpaba el corazón.

    La conquista y anexión del Imperio incaico tuvo lugar el 14 de noviembre de 1533 tras otra nueva alianza de los españoles con pueblos como las panacas huascaristas, cañaris, chachapoyas y otras etnias vasallas de los incas.

  • Encontrar un número de teléfono que puede estar en formato 123 456 789 o 123-456-789. La expreg. sería: /\d\d\d( |-)\d\d\d( |-)\d\d\d/
    También: /\d{3}( |-)\d{3}( |-)\d{3}/
    También: /\d{3}[-\s]\d{3}[-\s]\d{3}/
    Esta solución encontraría incluso 123456789: /\d{3}[-\s]?\d{3}[-\s]?\d{3}/

  • Encontrar cadenas que tienen un número indefinido de ‘zz’ y a continuación un número indefinido de ‘xx’. Ejemplo: zzxx zzzzxx zzxxx zx zzzxxxxx
    La expreg. sería: /(zz)+(xx)+/

  • Encontrar palabras que empiecen y terminen por “a”. La expreg.: /\<a\w*a\>/

  • Seleccionar dentro de un texto fechas con formato correcto (día/mes/año). El separador puede ser ‘/’ o ‘-’. El día y el mes puede ser de 1 o 2 dígitos. El año puede ser de 2 o 4 dígitos.
    La expreg. sería: /\b[0-9]{1,2}[/-][0-9]{1,2}[/-]([0-9]{2}|[0-9]{4})\b/
    Encontraría cosas como: 9-3-1964, 9-03-64, 09/03/1964, 9/3/1964
    No encontraría: 119-3-1964, 9-203-64, 09/03/13964

  • Comprobar si es una dirección MAC. La expreg.: /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/

    Ejemplo: 01:23:45:FD:C9:AB

  • Seleccionar las cadenas donde aparece “abierto por la” seguido de “mañana”, “tarde” o “noche”. La expreg.: /abierto por la (mañana|tarde|noche)/

    abierto por la mañana
    abierto por la tarde
    abierto por la gana
    abierto por la noche

  • Selecciona las cadenas donde aparece “moto” o “motocicleta” usando una agrupación. La expreg.: /moto(cicleta)?/
    Me compré una moto. Mi motocicleta se averió.

  • Seleccionar direcciones webs completas: /(http|https):\/\/([^\/\r\n\s]+)(\/[^\/\r\n\s]*)*/

    Visita la web https://regexr.com/ y no te arrepentirás

    Ve a http://micasa654321.com/blog/mayo/ejemplos/ también está bien

    https://www.asturias.es/ https/gnu.org

  • Validar si es una dirección de email es correcta. Esto es muy complejo, una posible solución es la siguiente: /^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/

  • En un listado de nombres con 1 o 2 apellidos, busco a las personas que se llamen “Ana” o “Ana María”. La expreg.: /^Ana (María )?\w+ \w+$/
    María Castro
    Ana María Rey
    Margarita Salas Falgueras
    Gabriela González

    Ana María Matute
    Francisco Pizarro

  • Busco a las personas que se llamen “María” o “Juan” con nombre compuesto y que tengan “Pérez” como único apellido.
    La expreg.: /^(María|Juan)\s(.*?)\s(Pérez)$/

    María José Pérez
    Hernán Cortés
    Álvaro Núñez Cabeza de Vaca

    María del Mar Pérez
    Juan José Pérez
    Blas de Lezo

  • Queremos contraseñas que cumplan tener más de 7 caracteres, con al menos una mayúscula, una minúscula, y un dígito: /(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}/
    Vamos a ver como está formada:

    • (?=.*[a-z]) => Aserción. Vale cualquier carácter y al menos una letra minúscula

    • (?=.*[A-Z]) => Aserción. Vale cualquier carácter y al menos una letra mayúscula

    • (?=.*\d) => Aserción. Vale cualquier carácter y al menos un dígito.

    • [a-zA-Z\d] => Buscamos una letra minúsculas, mayúscula o un dígito.

    • {8,} => Lo anterior, al menos 8 veces.

    Tener en cuenta que no funcionaría así: /[a-zA-Z\d]{8,}(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/

  • Buscar en un texto cadenas duplicadas (seguidas) como por ejemplo: Esa casa casa es guapa.
    La expreg.: /\b(\w+)\b\s+\1\b/

  • Queremos seleccionar códigos de 3 cifras que cumplan la siguiente condición: si el código comienza por 0, el siguiente carácter no puede ser otro 0.
    La expreg.: /^(0[1-9]\d|[1-9]\d\d)$/
    010
    01234
    001
    890
    21275
    601

    Una expreg. equivalente utilizando un condicional: ^(0)?(?(1)[1-9]\d|[1-9]\d{2})$
    La explicación:

    • Busca si hay un 0 al comienzo (^(0)).

    • Si hay el 0 inicial (?(1)[1-9]\d), lo siguiente tiene que ser un dígito del 1 al 9 y otro dígito cualquiera.

    • Si no lo hay, como es opcional, buscamos tres dígitos, el primero sería un dígito del 1 al 9 seguido de otros 2 dígitos cualquiera.

  • En un listado con cantidades que pueden estar en “caja A” o en “caja B”, busco todas las cantidades negativas de la “caja B”. La expreg.: /-\d+ (?= caja B)/
    432 cajaA
    -34 cajaA
    4 caja B
    -5 caja B
    3 cajaA
    345 caja B
    -654 caja B
    345 cajaA
    4 cajaA
    -45 caja B

  • Seleccionar dentro de un texto fechas con formato correcto (día/mes/año) y que sean válidas teniendo en cuenta el número de días de cada mes y los años bisiesto.
    La solución: ¡Olvídese! La expreg. tienen un límite a partir del cual dan más trabajo que beneficio.



Conclusión

Las expreg. son una herramienta muy potente a la hora de ayudarnos a realizar búsquedas complejas, pero también tienen sus límites. Muchas veces podemos encontrar varias soluciones válidas al mismo problema, pero hay que estar muy atentos a los resultados porque en ocasiones pueden aparecer cosas inesperadas. Siempre es bueno probar con un conjunto limitado de datos para cerciorarse de que la búsqueda funciona bien en todos los casos.

En su contra tienen que no funcionan igual en todos los sitios y llega un momento en que la complejidad pueda ser tan grande que lo mejor es no usarlas. Otro problema es que son fáciles de olvidar si no se utilizan a menudo. 

De todas formas merece la pena conocerlas y tal vez algún día le saquen de un apuro (o le metan en otro).

Tabla resumen

Metacaracter

Significado

\s


Sustituye a un espacio. Ej.: mal\s

Encuentra: Mi mal amigo. Qué mal.

\S

Equivale a cualquier carácter diferente de espacio. Ej.: mal\Sa

Encuentra:malva mal a esmaltado mal asaña

\d

Sustituye a un dígito (0 – 9). Ej.: cod-\d\d

Encuentra: cod-2 res-5 cod-88 cod-4 cod-437x cod22

\D

El contrario de ‘\d’. Ej.: \D

Encuentra: X=23*4

\w

Equivale a cualquier carácter alfanumérico y subrayado dentro del código ASCII. No encuentra la ‘ñ’, acentos, el espacio, etc. Ej.: \w
Encuentra: Noreña 12 camión

\W

El contrario de \w. Ej.: \W
Encuentra: Noreña 12 camión

\b


Para buscar palabras. Una letra, una barra baja o un número no marcan fin de cadena. Otros signos sí. Ej.: \bmal\b o \<mal\>

Encuentra: Mi mal amigo. Es malvado. Qué mal. Mal666. Remal.

\B

Lo contrario de \b. Equivale a cualquier carácter que no sea letra, número o subrayado. Ej.: /vien\B/

Encuentra: Hola, vienes mañana.

\<

\>

Como \b. Inicio o el fin de una palabra. No hay porqué usarlos juntos.

Ej.: \<mar\>

Encuentra: Amar la mar y no la marea.

.

Equivale a un carácter cualquiera, excepto ‘nueva línea’ (\n). Ej.: m.l

Encuentra: Mi mal es el mol.

?

Equivale a uno o ningún carácter como el que le precede. Ej.: ma?l

Encuentra: Mi ml es el mal. Voy a mil.

En combinación con ‘+’ o ‘*’ limita las coincidencias. Ej.: X.*?Z

Encuentra: asdfgX1234ZjklZ

Ej.: X.*Z

Encuentra: asdfgX1234ZjklZ

+


+?

Equivale a 1 o muchos caracteres como el que le precede. Ej.: ma+l

Enucentra: Mi maaaaal es el mal. Voy ml.

Limita las coincidencias. Ej. A.+Z

Encuentra: AlkZjñlasdfZ

En lugar de: AAlkZjñlasdfZ

*


*?

Equivale a 0 o muchos caracteres como el que le precede. Ej.: ma*l

Encuentra: Mi maaaaal es el mal. Voy ml.

Limita las coincidencias. Ej. A.*Z

Encuentra: AlkZjñlasdfZ

En lugar de: AZjñlasdfZ

{2} exactos

{2,5} entre 2 y 5

{2,} 2 o más

Encontrar un número concreto de caracteres. Ej.: a{2,3}

Encuentra: xxxaazz aaa a zzaaazaaza

^

Indica que lo buscado tiene que estar al comienzo de una línea.

Ej.: ^11

Encuentra: 11_soles 11_lunas.

$

Indica que lo buscado tiene que estar al final de una línea.

Ej.:11$

Encuentra: soles_11 lunas_11

|

Buscar varios. Ej: si|no

Encuentra: Si o no. Lasi. Cono

[ ]

Conjunto de caracteres independientes. Encuentra cualquiera de los caracteres del conjunto. Ej.: [aeiou]

Encuentra: murcielago

[^ ]

Negar conjunto. Encuentra cualquier carácter diferente de los incluidos.

Ej.: [^aeiou]

Encuentra: murcielago

()

Agrupación. Podemos aplicar las opciones de búsqueda a un conjunto de caracteres, a una agrupación.

Ej.: (19)+

Encuentra: 19abc 191xyz 1911abc 19191xyz

(?

Búsquedas alrededor de la zona y condicionales.


Algunas web para practicar con expresiones regulares:

http://regexr.com/

https://regexcrossword.com/

Aplicación móvil:

https://f-droid.org/repository/browse/?fdid=com.phikal.regex

Web con expresiones regulares ya realizadas para resolver búsquedas complejas:

https://uibakery.io/regex-library


No hay comentarios:

Publicar un comentario