lunes, 10 de agosto de 2020

Introducción a Regular Expressions: El comando grep

Las Expresiones Regulares, o Regular Expressions (RegEx), son patrones que podemos usar para encontrar una determinada combinación de caracteres dentro de un texto. Las RegEx proporcionan una manera muy flexible y poderosa de buscar o reconocer cadenas de texto para luego imprimirlas, modificarlas o cambiar su formato. 

Existen dos tipos de RegEx en el estándar POSIX: Basic Regular Expressions (BRE) y Extended Regular Expressions (ERE). Luego están las Perl Compatible Regular Expression (PCRE), escritas en Lenguaje C e inspiradas en el poder de Perl.

No debemos confundir RegEx con Shell Globbs, ya que éste ultimo trabaja solamente con nombres de archivos. Las RegEx fueron definidas en la década del 1950 y tienen su origen en Regular Events, una notación creada por Stephen C. Kleene, un matemático estadounidense.

Los lenguajes de programación más conocidos soportan RegEx: C, Ruby, XML, Visual Basic, Unix Shell, Java, JavaScript, Python y, por supuesto, Perl.

De ahora en adelante nos enfocaremos en el uso de RegEx en el Linux Shell (Bash) usando grep, sed y awk. Nuestra guía y referencia de estudio es este cheatsheet (chuleta, en español) y como texto objetivo usaremos el poema Hay Un País en el Mundo, del poeta nacional (dominicano) Pedro Mir (1913-2000).

El comando grep.

Este comando debe su existencia Ken Thompson, y su nombre en sí mismo una RegEx: g/re/p (globally search for a regular expression and print matching lines) es la herramienta para filtrado de texto por excelencia en entornos Unix/Linux. De hecho el Diccionario Oxford tiene una entrada de grep como verbo, debido a esto es posible decir "I'm going to grep my files".

Manos a la obra...

Cada idioma tiene sus signos y reglas de acentuación para darle sentido, la escritura y el sonido adecuado a las palabras. Por lo que en español no es lo mismo "pais" que "país":
$ grep "pais" hay-un-pais-en-el-mundo.txt 
y mártir de los tórridos paisajes
$ grep "país" hay-un-pais-en-el-mundo.txt 
Hay un país en el mundo
un país en el mundo
que en este fluvial país en que la tierra brota,
un país en el mundo
Es un país pequeño y agredido. Sencillamente triste,
vengo a hablar de un país.
Desterrado en su tierra. y un país,
Agregando la opción -n, grep nos muestra el número de la línea que coincidió con el patrón:
$ grep -n "país" hay-un-pais-en-el-mundo.txt 
1:Hay un país en el mundo
48:un país en el mundo
52:que en este fluvial país en que la tierra brota,
90:un país en el mundo
99:Es un país pequeño y agredido. Sencillamente triste,
104:vengo a hablar de un país.
163:Desterrado en su tierra. y un país,
La opción -c, no imprime las líneas que coinciden pero sí la suma total de ellas:
$ grep -c "país" hay-un-pais-en-el-mundo.txt 
7
En Linux has oído hablar de Case Sensitive. Eso significa que Donde y donde no es lo mismo para grep.
$ grep "Donde" hay-un-pais-en-el-mundo.txt 
Donde un ángel respira.
$ grep "donde" hay-un-pais-en-el-mundo.txt 
donde el día tiene su triunfo verdadero,
donde quiera, donde ruedan montañas por los valles
como frescas monedas azules, donde duerme
de donde el viento asalta el íntimo terrón
donde cada colina parece un corazón,
donde un campesino breve,
final de viaje donde una isla
y abrid los ojos donde un desastre
abre una herida donde unos ojos
no tienen sexo donde una patria
donde el hombre y la res y el surco duermen
donde arde
Para obtener ambos resultados usamos la opción -i (case insensitive):
$ grep -i "donde" hay-un-pais-en-el-mundo.txt 
donde el día tiene su triunfo verdadero,
donde quiera, donde ruedan montañas por los valles
como frescas monedas azules, donde duerme
de donde el viento asalta el íntimo terrón
donde cada colina parece un corazón,
donde un campesino breve,
final de viaje donde una isla
y abrid los ojos donde un desastre
abre una herida donde unos ojos
no tienen sexo donde una patria
donde el hombre y la res y el surco duermen
Donde un ángel respira.
donde arde
Por defecto grep imprime todas las líneas que contienen el patrón que buscamos:
$ grep "Pero" hay-un-pais-en-el-mundo.txt 
Pero no.
Pero no.
Pero no.
Pero
Pero ebrio de orégano y de anís,
Usaremos la opción -x para que solo nos muestre las líneas que son el patrón exactamente:
$ grep -x "Pero" hay-un-pais-en-el-mundo.txt 
Pero
Existe el término "inverted match", consiste en imprimir las excepciones al patrón de búsqueda, usando la opción -v:
$ grep -v "Pedro" hay-un-pais-en-el-mundo.txt
El comando anterior imprimirá todo el contenido del fichero "hay-un-pais-en-el-mundo.txt" excepto la última línea que contiene el patrón de búsqueda "Pedro". 

Podemos usar una RegEx para imprimir las líneas que terminan en "tierra.". El carácter especial "$" indica final de la línea.
$ grep "tierra.$" hay-un-pais-en-el-mundo.txt 
los campesinos no tienen tierra.
los campesinos no tienen tierra.
los campesinos no tienen tierra.
los campesinos no tienen tierra.
no tienen tierra no tienen tierra.
Traficante de tierras y sin tierra.
Para usar RegEx en el comando grep es necesario usar la opción -E, de Extended Regular Expressions. Para imprimir las líneas que tienen la palabra "piedra" o la palabra "palma":
$ grep -E 'piedra|palma' hay-un-pais-en-el-mundo.txt 
que se detiene junto a una piedra
de constructiva paz en cada palma.
La siguiente RegEx imprime las palabras de tres letras con la primera en mayúscula:
$ grep -E '[A-Z]{1}\w{2}\b' hay-un-pais-en-el-mundo.txt
Hay un país en el mundo
Con tres millones
Hay
Hay
Los que la roban no tienen ángeles
Pedro Mir (1913-2000).
Esta RegEx imprime las palabras de trece letras que comienzan con mayúscula, además con la opción -o solo muestra el patrón que coincide sin el resto del texto que compone la línea:
$ grep -o -E '[A-Z]\w{12}\b' hay-un-pais-en-el-mundo.txt 
Sencillamente
Sencillamente
Sencillamente
Sencillamente
Sencillamente
Sencillamente
En el siguiente comando tenemos una RegEx que va a encontrar cualquier secuencia de 4 dígitos, un guión, seguido por cuatro dígitos.
$ grep -E '([0-9]{4}-[0-9]{4})' hay-un-pais-en-el-mundo.txt 
Pedro Mir (1913-2000).
Pero, si se dieron cuenta, aunque la RegEx tiene los paréntesis, grep no los marcó como resultado de la búsqueda. Esto se debe a que los paréntesis son caracteres especiales en RegEx que sirven para agrupar expresiones. Si deseamos que sean parte del patrón de búsqueda es necesario "escaparlos" usando "\".
$ grep -E '\([0-9]{4}-[0-9]{4}\)' hay-un-pais-en-el-mundo.txt 
Pedro Mir (1913-2000).
grep puede recibir entrada y enviar su salida desde y hacia otros comandos usando pipes o tuberías. Para profundizar más sobre el comando grep puedes consultar su manual:
$ man grep

No hay comentarios:

Publicar un comentario