EXPRESIONES REGULARES y COMMANDO grep

Fuente:  http://blog.desdelinux.net/con-el-terminal-uso-de-expresiones-regulares/

 

¿Qué es una expresión regular?

Una expresión regular es una serie de caracteres especiales que permiten describir un texto que queremos buscar. Por ejemplo, si quisiéramos buscar la palabra “linux” bastaría poner esa palabra en el programa que estemos usando. La propia palabra es una expresión regular. Hasta aquí parece muy simple, pero, ¿y si queremos buscar todos los números que hay en un determinado fichero? ¿O todas las lineas que empiezan por una letra mayúscula? En esos casos ya no se puede poner una simple palabra. La solución es usar una expresión regular.


Expresiones regulares vs patrones de ficheros.

Antes de empezar a entrar en materia sobre las expresiones regulares, quiero aclarar un malentendido común sobre las expresiones regulares. Una expresión regular no es lo que ponemos como parámetro en los comandos como rm, cp, etc para hacer referencia a varios fichero que hay en el disco duro. Eso sería un patrón de ficheros. Las expresiones regulares, aunque se parecen en que usan algunos caracteres comunes, son diferentes. Un patrón de fichero se lanza contra los ficheros que hay en el disco duro y devuelve los que encajan completamente con el patrón, mientras que una expresión regular se lanza contra un texto y devuelve las lineas que contienen el texto buscado. Por ejemplo, la expresión regular correspondiente al patrón *.* seria algo así como ^.*\..*$

Tipos de expresiones regulares.

No todos los programas utilizan las mismas expresiones regulares. Ni mucho menos. Existen varios tipos de expresiones regulares más o menos estándar, pero hay programas que cambian ligeramente la sintaxis, que incluyen sus propias extensiones o incluso que utilizan unos caracteres completamente diferentes. Por eso, cuando queráis usar expresiones regulares con algún programa que no conozcáis bien, lo primero es mirar el manual o la documentación del programa para ver cómo son las expresiones regulares que reconoce.
En primer lugar, existen dos tipos principales de expresiones regulares, que están recogidas en el estándar POSIX, que es el que usan las herramientas de Linux. Son las expresiones regulares básicas y las extendidas. Muchos de los comandos que trabajan con expresiones regulares, como grep o sed, permiten usar estos dos tipos. Más abajo hablare de ellos. También están las expresiones regulares estilo PERL, y luego hay programas como vim o emacs que usan variantes de estas. Según lo que queramos hacer puede ser más adecuado usar unas u otras.

Probando expresiones regulares.

La sintaxis de las expresiones regulares no es nada trivial. Cuando tengamos que escribir una expresión regular complicada estaremos delante de una ristra de caracteres especiales imposibles de entender a primera vista, así que para aprender a usarlas es imprescindible contar con una forma de hacer todas las pruebas que queramos y ver los resultados fácilmente. Por eso voy a poner ahora varios comandos con los que podremos hacer las pruebas y experimentar todo lo que necesitemos hasta que tengamos las expresiones regulares dominadas.
El primero de ellos es el comando grep. Este es el comando que usaremos con más frecuencia para hacer búsquedas. La sintaxis es la siguiente:

grep [-E] 'REGEX' FICHERO
COMANDO | grep [-E] 'REGEX'

Recomiendo poner siempre las expresiones regulares entre comillas simples para que el shell no nos haga de las suyas. La primera forma sirve para buscar una expresión regular en un fichero. La segunda permite filtrar la salida de un comando a través de una expresión regular. Por defecto, grep usa expresiones regulares básicas. La opción -E es para usar expresiones regulares extendidas.
Un truco que nos puede ayudar a ver el funcionamiento de las expresiones regulares es activar el uso del color en el comando grep. De esa manera, aparecerá resaltada la parte del texto que empareja con la expresión regular que estamos usando. Para activar el color en el comando grep basta con asegurarse que la variable de entorno GREP_OPTIONS contenga en valor --color, cosa que se puede hacer con este comando:
GREP_OPTIONS=--color
Podemos ponerlo en el .bashrc para tenerlo activado siempre.
Otra forma de usar expresiones regulares es mediante el comando sed. Este es más adecuado para reemplazar texto, pero también puede usarse para hacer búsquedas. La sintaxis para ello sería así:

sed -n[r] '/REGEX/p' FICHERO
COMANDO | sed -n[r] '/REGEX/p'

El comando sed también usa expresiones regulares básicas por defecto, se pueden usar expresiones regulares extendidas con la opción -r.
Otro comando que también quiero nombrar es awk. Este comando puede usarse para muchas cosas, ya que permite escribir scripts en su propio lenguaje de programación. Si lo que queremos es buscar una expresión regular en un fichero o en la salida de un comando, la forma de usarlo sería la siguiente:

awk '/REGEX/' FICHERO
COMANDO | awk '/REGEX/'
Este comando usa siempre expresiones regulares extendidas.
Para hacer nuestras pruebas también necesitaremos un texto que nos sirva como ejemplo para hacer búsquedas en él. Podemos utilizar el siguiente texto:
- Lista de páginas wiki:

ArchLinux: https://wiki.archlinux.org/
Gentoo: https://wiki.gentoo.org/wiki/Main_Page
CentOS: http://wiki.centos.org/
Debian: https://wiki.debian.org/
Ubuntu: https://wiki.ubuntu.com/

- Fechas de lanzamiento:

Arch Linux: 11-03-2002
Gentoo: 31/03/2002
CentOs: 14-05-2004 03:32:38
Debian: 16/08/1993
Ubuntu: 20/10/2004

Desde Linux Rulez.
Este es el texto que usaré para los ejemplos del resto del post, así que os recomiendo que lo copiéis en un fichero para tenerlo a mano desde la terminal. Podéis poner el nombre que queráis. Yo lo he llamando regex.

Entrando en materia.

Ahora ya tenemos todo lo necesario para empezar a probar las expresiones regulares. Vayamos poco a poco. Voy a poner varios ejemplos de búsquedas con expresiones regulares en los que iré explicando para qué sirve cada carácter. No son ejemplos muy buenos, pero como me va a quedar un post muy largo no quiero complicarlo más. Y eso que sólo voy a arañar la superficie de lo que se puede hacer con expresiones regulares.
Lo más sencillo de todo es buscar una palabra concreta, por ejemplo, supongamos que queremos buscar todas las lineas que contengan la palabra “Linux”. Esto es lo más fácil, ya que sólo tenemos que escribir:
grep 'Linux' regex

Y podremos ver el resultado:

ArchLinux: https://wiki.archlinux.org/
Arch Linux: 11-03-2002
Desde Linux Rulez.
 
Estas son las tres lineas que contienen la palabra “Linux” la cual, si hemos usado el truco del color, aparecerá resaltada. Fijaros que reconoce la palabra que estamos buscando aunque forme parte de una palabra más larga como en “ArchLinux”. Sin embargo, no resalta la palabra “linux” que aparece en la URL “https://wiki.archlinux.org/”. Eso es porque ahí aparece con la “l” minúscula y la hemos buscado en mayúscula. El comando grep tiene opciones para esto, pero no voy a hablar de ellas en un artículo que trata sobre expresiones regulares.
Con esta sencilla prueba podemos sacar la primera conclusión:
  • Un carácter normal puesto en una expresión regular empareja consigo mismo.
Lo que viene a decir que si pones la letra “a” buscará la letra “a”. Parece lógico, ¿verdad? :)
Supongamos ahora que queremos buscar la palabra “CentO” seguida de cualquier carácter, pero sólo un único carácter. Para esto podemos usar el carácter “.”, que es un comodín que empareja con un carácter cualquiera, pero sólo uno:

grep 'CentO.' regex
Y el resultado es:

CentOS: http://wiki.centos.org/
CentOs: 14-05-2004 03:32:38
 
Lo que significa que incluye la “S” de “CentOS” aunque en un caso es mayúscula y en otro minúscula. Si apareciera en ese lugar cualquier otro carácter también lo incluiría. Ya tenemos la segunda regla:
  • El carácter “.” empareja con cualquier carácter.
Ya no es tan trivial como parecía, pero con esto no podemos hacer mucho. Avancemos un poco más. Vamos a suponer que queremos encontrar las líneas en las que aparece el año 2002 y el 2004. Parecen dos búsquedas, pero se pueden hacer de una sola vez así:
grep '200[24]' regex

Lo que quiere decir que queremos buscar el número 200 seguido del 2 o el 4. Y el resultado es este:

Arch Linux: 11-03-2002
Gentoo: 31/03/2002
CentOs: 14-05-2004 03:32:38
Ubuntu: 20/10/2004
Lo que nos lleva a la tercera regla:
  • Varios caracteres encerrados entre corchetes emparejan con cualquiera de los caracteres que hay dentro de los corchetes.
Los corchetes dan más juego. también se pueden usar para excluir caracteres. Por ejemplo, supongamos que queremos buscar los sitios en los que aparece el carácter “:”, pero que no vaya seguido de “/”. El comando sería así:
grep ':[^/]' regex
Se trata simplemente de poner un “^” como primer carácter dentro del corchete. Se pueden poner a continuación todos los caracteres que se quieran. El resultado de este último comando es el siguiente:

ArchLinux: https://wiki.archlinux.org/
Gentoo: https://wiki.gentoo.org/wiki/Main_Page
CentOS: http://wiki.centos.org/
Debian: https://wiki.debian.org/
Ubuntu: https://wiki.ubuntu.com/
Arch Linux: 11-03-2002
Gentoo: 31/03/2002
CentOs: 14-05-2004 03:32:38
Debian: 16/08/1993
 Ubuntu: 20/10/2004
 
Ahora aparecen resaltados los “:” que hay detrás de los nombres de las distros, pero no los que hay en las URL porque los de las URL llevan “/” a continuación.
  • Poner el carácter “^” al principio de un corchete empareja con cualquier carácter excepto con los demás caracteres del corchete.
Otra cosa que podemos hacer es especificar un rango de caracteres. Por ejemplo, para buscar cualquier número seguido de un “-” sería así:
grep '[0-9]-' regex

Con esto estamos especificando un carácter entre 0 y 9 y, a continuación, un signo menos. Veamos el resultado:

Arch Linux: 11-03-2002
CentOs: 14-05-2004 03:32:38
 
Se pueden especificar varios rangos dentro de los corchetes a incluso mezclar rangos con caracteres sueltos.
  • Colocar dos caracteres separados por “-” dentro de los corchetes empareja con cualquier carácter dentro del rango.
Vamos a ver ahora si podemos seleccionar la primera parte de las URL. La que pone “http” o “https”. Sólo se diferencian en la “s” final, así que vamos a hacerlo de la siguiente manera:

grep -E 'https?' regex

La interrogación sirve para hacer que el carácter que hay a su izquierda sea opcional. Pero ahora hemos añadido la opción -E al comando. Esto es porque la interrogación es una característica de las expresiones regulares extendidas. Hasta ahora estábamos usando expresiones regulares básicas, así que no hacía falta poner nada. Veamos el resultado:

ArchLinux: https://wiki.archlinux.org/
Gentoo: https://wiki.gentoo.org/wiki/Main_Page
CentOS: http://wiki.centos.org/
Debian: https://wiki.debian.org/
Ubuntu: https://wiki.ubuntu.com/
O sea que ya tenemos una nueva regla:
  • Un carácter seguido de “?” empareja con ese carácter o con ninguno. Esto sólo es válido para expresiones regulares extendidas.
Ahora vamos a buscar dos palabras completamente diferentes. Vamos a ver cómo buscar las lineas que contengan tanto la palabra “Debian” como “Ubuntu”.

grep -E 'Debian|Ubuntu' regex

Con la barra vertical podemos separar dos o más expresiones regulares diferentes y buscar las lineas que emparejen con cualquiera de ellas:

Debian: https://wiki.debian.org/
Ubuntu: https://wiki.ubuntu.com/
Debian: 16/08/1993
Ubuntu: 20/10/2004
  • El carácter “|” sirve para separar varias expresiones regulares y empareja con cualquiera de ellas. También es específico de las expresiones regulares extendidas.

 Para buscar una palabra que comienze por un caracter determinado colocamos el caracter  ^ delante.  Ejemplo:

$ grep '^#' /etc/default/grub
 
 
-v  realiza la accion inversa:
$ grep -v  '^#' /etc/default/grub
 
 
 
 

Continuemos. Ahora vamos a buscar la palabra “Linux”, pero sólo donde no esté pegada a otra palabra por la izquierda. Podemos hacerlo así:
grep '\<Linux'

Aquí el carácter importante es “<“, pero es necesario escaparlo colocando “\” delante para que grep lo interprete como un carácter especial. El resultado es el siguiente:

Arch Linux: 11-03-2002
Desde Linux Rulez.

También se puede usar “\>” para buscar palabras que no estén pegadas a otras por la derecha. Vamos con un ejemplo. Probemos este comando:

grep 'http\>' regex

El resultado que produce es este:
 
CentOS: http://wiki.centos.org/

Ha salido “http”, pero no “https”, porque en “https” todavía hay un carácter a la derecha de la “p” que puede formar parte de una palabra.
  • Los caracteres “<” y “>” emparejan con el principio y el final de una palabra, respectivamente. Hay que escapar estos caracteres para que no se interpreten como caracteres literales.
Vamos con cosas un poco más complicadas. El carácter “+” empareja con el carácter que aparece a su izquierda repetido al menos una vez. Este carácter sólo está disponible con las expresiones regulares extendidas. Con él podemos buscar, por ejemplo, secuencias de varios números seguidos que empiecen con “:”.

grep -E ':[0-9]+' regex

Resultado:
 
CentOs: 14-05-2004 03:32:38

Queda resaltado también el número 38 porque también empieza con “:”.
  • El carácter “+” empareja con el carácter que aparece a su izquierda repetido al menos una vez.
También se puede controlar el número de repeticiones usando “{” y “}”. La idea es colocar entre llaves un número que indica el número exacto de repeticiones que queremos. También se puede poner un rango. Vamos a ver ejemplos de los dos casos.
Primero vamos a buscar todas las secuencias de cuatro dígitos que haya:

grep '[0-9]\{4\}' regex

Fijaros en que hay que escapar las llaves si estamos usando expresiones regulares básicas, pero no si usamos las extendidas. Con extendidas sería así:

grep -E '[0-9]{4}' regex

Y el resultado en los dos casos sería este:
 
Arch Linux: 11-03-2002
Gentoo: 31/03/2002
CentOs: 14-05-2004 03:32:38
Debian: 16/08/1993
Ubuntu: 20/10/2004
  • Los caracteres “{” y “}” con un número entre ellos emparejan con el carácter anterior repetido el número de veces indicado.
Ahora el otro ejemplo con las llaves. Supongamos que queremos encontrar palabras que tengan entre 3 y 6 letras minúsculas. Podríamos hacer lo siguiente:

grep '[a-z]\{3,6\}' regex

Y el resultado sería este:
- Lista de páginas wiki:
ArchLinux: https://wiki.archlinux.org/
Gentoo: https://wiki.gentoo.org/wiki/Main_Page
CentOS: http://wiki.centos.org/
Debian: https://wiki.debian.org/
Ubuntu: https://wiki.ubuntu.com/
- Fechas de lanzamiento:
Arch Linux: 11-03-2002
Gentoo: 31/03/2002
CentOs: 14-05-2004 03:32:38
Debian: 16/08/1993
Ubuntu: 20/10/2004
Desde Linux Rulez.

Que, como veis, no se parece mucho a lo que queríamos. Eso es porque la expresión regular encuentra las letras dentro de otras palabras que son más largas. Probemos con esta otra versión:

grep '\<[a-z]\{3,6\}\>' regex

Resultado:
- Lista de páginas wiki:
ArchLinux: https://wiki.archlinux.org/
Gentoo: https://wiki.gentoo.org/wiki/Main_Page
CentOS: http://wiki.centos.org/
Debian: https://wiki.debian.org/
Ubuntu: https://wiki.ubuntu.com/

Esto ya se parece más a lo queríamos. Lo que hemos hecho es exigir que la palabra empiece justo delante de la primera letra y termine justo detrás de la última.
  • Los caracteres “{” y “}” con dos números entre ellos separados por una coma emparejan con el carácter anterior repetido el número de veces indicado por los dos números.
Veamos ahora un carácter que es primo de “+”. Se trata de “*” y su funcionamiento es muy parecido sólo que empareja con cualquier número de caracteres incluido cero. O sea que hace lo mismo que el “+” pero no exige que el carácter de su izquierda aparezca en el texto. Por ejemplo, probemos a buscar esas direcciones que empiezan en wiki y acaban en org:

grep 'wiki.*org' regex
Vamos a ver el resultado:

ArchLinux: https://wiki.archlinux.org/
Gentoo: https://wiki.gentoo.org/wiki/Main_Page
CentOS: http://wiki.centos.org/
Debian: https://wiki.debian.org/

Perfecto.
Ahora el último carácter que vamos a ver. El carácter “\” sirve para escapar el carácter de su derecha de manera que pierda el significado especial que tiene. Por ejemplo: Supongamos que queremos localizar la lineas que terminen en punto. Lo primero que se nos podría ocurrir podría ser esto:

grep '.$' regex

El resultado no es el que buscamos:
 
- Lista de páginas wiki:
ArchLinux: https://wiki.archlinux.org/
Gentoo: https://wiki.gentoo.org/wiki/Main_Page
CentOS: http://wiki.centos.org/
Debian: https://wiki.debian.org/
Ubuntu: https://wiki.ubuntu.com/
- Fechas de lanzamiento:
Arch Linux: 11-03-2002
Gentoo: 31/03/2002
CentOs: 14-05-2004 03:32:38
Debian: 16/08/1993
Ubuntu: 20/10/2004
Desde Linux Rulez.

Esto es porque el carácter “.” empareja con cualquier cosa, así que esa expresión regular empareja con el último carácter de cada linea sea cual sea. La solución es esta:

grep '\.$' regex

Ahora el resultado sí que es el que queremos:
 
Desde Linux Rulez.
 
 
 

Expresiones regulares básicas de un solo carácter



Expresión regularSe empareja con....
.Cualquier carácter
[ ]Cualquiera de los caracteres que estén dentro de los corchetes
[^ ]Cualquiera de los caracteres que NO estén dentro de los corchetes
^El principio de línea
$El final de la línea
*Cero o mas ocurrencias de la expresión anterior (izquierda)
\( \)Permite agrupar varias expresiones regulares
\Escapa un metacarácter


Estas expresiones tienen las siguientes reglas especiales:

  • Dentro de los corchetes [ ] los metacaracteres pierden su función especial y se tratan como literales.
  • Hay varias excepciones a la regla anterior. Por ejemplo el carácter del “sombrerito” si se pone al inicio del corchete gana una funcionalidad diferente, como hemos visto en la tabla anterior.

Expresiones regulares básicas de repetición


Podemos repetir una expresión regular tantas veces como queramos con la secuencia \{ \}. Esta repetición se puede realizar de las siguientes formas:

ConstructorResultado
\{n\}Concuerda exactamente con n ocurrencias de la expresión anterior
\{n,\}Concuerda con la menos ocurrencias de la expresión anterior
\{n, m\}Concuerda con entre m ocurrencias de la expresión anterior

Vamos a poner un ejemplo  de este tipo de expresiones. Por ejemplo para buscar números de tres cifras podemos utilizar [0-9]\{3\}.  Esto significa que repetirá 3 veces la búsqueda de números del cero al nueve, podría igualarse a [0-9] [0-9] [0-9]

Expresiones regulares extendidas


Como hemos visto antes, para poder utilizar estas expresiones es necesario darle al comando que se ejecuta el soporte para poder usarlas. Antes de comenzar a verlas, es necesario ver reglas de conversión:
  • El uso de las barras invertidas en los corchetes y los paréntesis no sirve en las expresiones regulares extendidas. Esto quiere decir que lo que hemos visto antes de \( \) \{ \} ahora se debe poner de este modo: ( ) { }. Si queremos que sean caracteres literales es necesario escaparlos.

Expresiones regulares extendidas de un solo carácter


En este grupo se agregaria estas  expresiones:

Expresión regularConcuerda con...
+Una o mas ocurrencias de la expresión anterior (izquierda)
?Cero o una ocurrencia de la expresión anterior (izquierda)



Otros metacaracteres utilizados en las expresiones regulares extendidas


Existen otros metacaracteres para las expresiones regulares extendidas, entre los cuales solamente vamos a ver uno muy utilizado que hace referencia a la delimitación entre el principio y el fin de una palabra. Para referenciar el principio se debe usar \< y para referenciar el final \>
 

Comentarios