Chef vs Puppet vs Ansible vs Saltstack
Estrategias de ramificación: Git-flow / trunk-based development

Expresiones Regulares con el comando grep

patron expresiones regulares

¿Te preguntas qué hacen esas extrañas cadenas de símbolos en Linux? ¡Pues es magia en la línea de comandos! En este artículo vamos a lanzar hechizos con expresiones regulares (regex).

Además de Linux, las expresiones regulares se utilizan en una amplia variedad de contextos y plataformas en el campo de la informática y la programación. Algunos ejemplos de dónde más se usan son:

  • Programación en General: Son una herramienta común en muchos lenguajes de programación, como Python, JavaScript, Java, etc.
  • Editores de Texto y IDEs: Muchos editores de texto y entornos de desarrollo integrados (IDEs) ofrecen soporte para expresiones regulares.
  • Bases de Datos: Algunas bases de datos y sistemas de gestión de bases de datos (DBMS) permiten el uso de expresiones regulares en consultas y búsquedas.
  • Validación de Entrada de Usuario: Las regex son valiosas para validar la entrada del usuario en formularios y aplicaciones.
  • Web Scraping: Cuando necesitas extraer información de sitios web, las expresiones regulares son unas grandes aliadas para buscar y capturar patrones específicos de datos en el contenido HTML.
  • Análisis de Log: En el análisis de registros y archivos de registro, las regex son esenciales para buscar patrones específicos y extraer información relevante.
  • Automatización de Tareas: Las expresiones regulares son útiles en scripts y programas que automatizan tareas, como la reorganización de archivos, el formateo de datos y más.

Y podrás utilizarlas en muchos sitios más, por ejemplo, ya lo vimos en la «guía del comando sed». Aquí nos vamos a centrar a usarlas con el comando grep en una terminal linux. Para ver más sobre comandos de búsqueda en Linux: «grep, find y locate. Comandos de búsqueda en Linux»

¿Qué Son las Expresiones Regulares?

Las expresiones regulares (regex) son una forma de encontrar secuencias de caracteres coincidentes. Utilizan letras y símbolos para definir patrones para buscar en un archivo o flujo de datos. Existen varios sabores diferentes de regex. Vamos a analizar la versión utilizada en utilidades y comandos comunes de Linux, con ejemplos del comando grep que imprime líneas que coinciden con un patrón de búsqueda.

Cheat sheet Regex

Se han escrito libros enteros sobre las regex, por lo que este tutorial es simplemente una introducción. Existen regex básicas y extendidas, y aquí utilizaremos las extendidas.

Para usar las expresiones regulares extendidas con grep, debes emplear la opción -E (extendida). Debido a que esto se vuelve tedioso muy rápido, se creó el comando egrep, que es la combinación de grep -E.

Si encuentras más conveniente usar egrep, puedes hacerlo. Sin embargo, ten en cuenta que está oficialmente en desuso. Aunque aún está presente en todas las distribuciones que revisamos, podría desaparecer en el futuro.

Por supuesto, siempre puedes crear tus propios alias, de manera que tus opciones favoritas estén siempre disponibles para ti.

Regular Expressions

Comencemos

Para nuestros ejemplos, utilizaremos un archivo de texto plano que contiene una lista de nombres. Recuerda que puedes usar expresiones regulares con muchos comandos de Linux. Solo estamos usando grep como una forma conveniente de demostrarlas.

Aquí tienes el contenido del archivo que le llamaré proof_regex.txt:

Tim Brooks
Catherine Chang
Joel Cornell
Michael Crider
Manu Vergara
Justin Duino
Benj Edwards
Jason Fitzpatrick
Amanda Gambill
Sofia Gomez
Walter Glenn
Harry Guinness
Marshall Gunnell
Lowell Heddings
Andrew Heinzman
Josh Hendrickson
Tom Westrick
Chris Hoffman
Armando Guerra
Akemi Iwaya
Team regex
Dave Johnson
Dave McKay
Jessica Hermosilla
Alan Murray
Khamosh Pathak

fin.

Comencemos con un patrón de búsqueda simple y busquemos en el archivo las ocurrencias de la letra «o». Nuevamente, debido a que estamos utilizando la opción -E (expresiones regulares extendidas) en todos nuestros ejemplos, escribimos lo siguiente:

grep -E 'o' proof_regex.txt

Una imagen vale más que mil palabras.

expresiones regulares regex

Se muestra cada línea que contiene el patrón de búsqueda, y la letra coincidente está resaltada. Hemos realizado una búsqueda simple, sin restricciones. No importa si la letra aparece más de una vez, al final de la cadena, dos veces en la misma palabra o incluso junto a sí misma.

Un par de nombres tenían dos letras «o», si tan solo queremos esto en el resultado usamos el siguiente comando:

grep -E 'oo' proof_regex.txt

Nuestro conjunto de resultados, como era de esperar, es mucho más pequeño y nuestro término de búsqueda se interpreta literalmente. No significa nada más que lo que escribimos: dos caracteres «o» seguidos.

Veremos más funcionalidades con nuestros patrones de búsqueda a medida que avanzamos.

Números de Línea

Si deseas que grep liste el número de línea de las entradas coincidentes, puedes usar la opción -n (número de línea). Esto es un truco de grep, no forma parte de la funcionalidad de las expresiones regulares. Sin embargo, a veces puede ser útil saber dónde se encuentran las entradas coincidentes en un archivo.

Escribimos lo siguiente:

grep -E -n 'o' proof_regex.txt

Otro útil truco con grep que puedes utilizar es la opción -o (solo coincidencia). Esto muestra solo la secuencia de caracteres coincidentes, sin el texto circundante. Esto puede ser útil si necesitas escanear rápidamente una lista en busca de coincidencias duplicadas en cualquiera de las líneas.

Para hacerlo, escribimos lo siguiente:

grep -E -n -o 'o' proof_regex.txt

Si deseas reducir la salida al mínimo, puedes usar la opción -c (cuenta).

Escribimos lo siguiente para ver el número de líneas en el archivo que contienen coincidencias:

grep -E -c 'o' proof_regex.txt

El Operador de Alternancia

Si deseas buscar ocurrencias tanto de «ll» como de «oo», puedes usar el carácter de barra vertical (|), que es el operador de alternancia. Busca coincidencias tanto para el patrón de búsqueda a su izquierda como a su derecha.

Escribimos lo siguiente:

grep -E -n -o 'll|oo' proof_regex.txt

Cualquier línea que contenga una «ll», una «oo» o ambas aparecerá en los resultados.

Case Sensitivity

También puedes usar el operador de alternancia para crear patrones de búsqueda, como este:

am|Am

Esto coincidirá tanto con «am» como con «Am». Para cualquier cosa que no sean ejemplos triviales, esto rápidamente conduce a patrones de búsqueda engorrosos. Una forma sencilla de evitar esto es usar la opción -i (ignorar mayúsculas/minúsculas) con grep.

Para hacerlo, escribimos lo siguiente:

grep -E -i 'am' proof_regex.txt

expresiones regulares regex case sensitive

En la imagen se puede ver como el primer comando resalta dos resultados y el segundo comando tres resultados.

Anclaje

Podemos hacer coincidir la secuencia «Am» de otras formas también. Por ejemplo, podemos buscar ese patrón específicamente o ignorar la mayúscula/minúscula, y especificar que la secuencia debe aparecer al comienzo de una línea.

Cuando haces coincidir secuencias que aparecen en una parte específica de una línea de caracteres o una palabra, se llama anclaje. Utilizas el símbolo de circunflejo (^) para indicar que el patrón de búsqueda solo debe considerar una secuencia de caracteres una coincidencia si aparece al comienzo de una línea.

Escribimos lo siguiente (nota que el circunflejo está dentro de las comillas simples):

grep -E -i '^am' proof_regex.txt

Ambos de estos comandos coinciden con «Am» y el resultado nos muestra tan solo el que está al inicio de la línea.

Ahora, busquemos líneas que contengan una doble «n» al final de una línea.

Escribimos lo siguiente, usando el signo de dólar ($) para representar el final de la línea:

grep -E -i 'nn$'

Comodines

Puedes usar un punto (.) para representar cualquier carácter único.

Escribimos lo siguiente para buscar patrones que comiencen con «T», terminen con «m» y tengan un solo carácter entre ellos:

grep -E 'T.m' proof_regex.txt

El patrón de búsqueda coincide con las secuencia «Tim». También puedes repetir los puntos para indicar un cierto número de caracteres.

Escribimos lo siguiente para indicar que no nos importa cuáles son los tres caracteres intermedios:

grep -E 'J..n' proof_regex.txt

Coincide con la línea que contiene «Johnson».

Ahora bien, tendremos otro resultado si usamos el asterisco (*) para coincidir con cero o más ocurrencias del carácter precedente. En siguiente ejemplo, el carácter que precederá al asterisco es el punto (.), que (de nuevo) significa cualquier carácter.

Esto significa que el asterisco (*) coincidirá con cualquier número (incluido cero) de ocurrencias de cualquier carácter.

El asterisco a veces es confuso para los recién llegados a las regex. Esto es quizás porque generalmente lo usan como un comodín que significa «cualquier cosa».

Sin embargo, en las regex, ‘c*t’ no coincide con «cat», «cot», «coot», etc. En realidad, se traduce como «coincide con cero o más caracteres ‘c’, seguidos de un ‘t’». Por lo tanto, coincide con «t», «ct», «cct», «ccct» o cualquier número de caracteres «c».

Dado que conocemos el formato del contenido en nuestro archivo, podemos agregar un espacio como último carácter en el patrón de búsqueda. Un espacio solo aparece en nuestro archivo entre los nombres y apellidos.

Entonces, escribimos lo siguiente para forzar la búsqueda para incluir solo los nombres en el archivo:

grep -E 'J.*n ' proof_regex.txt

A primera vista, los resultados del primer comando parecen incluir algunas coincidencias extrañas. Sin embargo, todas coinciden con las reglas del patrón de búsqueda que hemos construido.

La secuencia debe comenzar con una «J» mayúscula, seguida de cualquier número de caracteres y luego una «n». Aunque todas las coincidencias comienzan con «J» y terminan con una «n», algunas de ellas no son lo que podrías esperar.

Debido a que agregamos el espacio en el segundo patrón de búsqueda, obtuvimos lo que pretendíamos: todos los nombres que comienzan con «J» y terminan en «n».

Clases de Caracteres

Digamos que queremos encontrar todas las líneas que comienzan con una «N» mayúscula o una «W» mayúscula.

Si usamos el siguiente comando, coincidirá con cualquier línea con una secuencia que comience con una «N» mayúscula o una «W» mayúscula, sin importar dónde aparezca en la línea:

grep -E 'N|W' proof_regex.txt

Eso no es lo que queremos. Si aplicamos el ancla de inicio de línea (^) al principio del patrón de búsqueda, como se muestra a continuación, obtendremos el mismo conjunto de resultados, pero por una razón diferente:

grep -E '^M|W' proof_regex.txt

La búsqueda coincide con líneas que contienen una «W» mayúscula en cualquier parte de la línea. También coincide con la línea «No more» porque comienza con una «N» mayúscula. El ancla de inicio de línea (^) solo se aplica a la «N» mayúscula.

También podríamos agregar un ancla de inicio de línea a la «W» mayúscula, pero eso pronto se volvería ineficiente en un patrón de búsqueda más complicado que nuestro ejemplo simple.

La solución es encerrar parte de nuestro patrón de búsqueda entre corchetes ([]) y aplicar el operador de anclaje al grupo. Los corchetes ([]) significan «cualquier carácter de esta lista». Esto significa que podemos omitir el operador de alternancia (|) porque no lo necesitamos.

Podemos aplicar el operador de anclaje de inicio de línea a todos los elementos de la lista dentro de los corchetes ([]). (Nota que el operador de anclaje de inicio de línea está fuera de los corchetes).

Escribimos lo siguiente para buscar cualquier línea que comience con una «N» mayúscula o una «W» mayúscula:

grep -E '^[MW]' proof_regex.txt

Usaremos estos conceptos en el siguiente conjunto de comandos también.

Escribimos lo siguiente para buscar a cualquiera que su nombre contenga ‘Jas’ o ‘Jes’:

grep -E 'J[ae]s' proof_regex.txt

Si el circunflejo (^) es el primer carácter en los corchetes ([]), el patrón de búsqueda busca cualquier carácter que no aparezca en la lista.

Por ejemplo, escribimos lo siguiente para buscar cualquier nombre que comience con «J», termine en «s» y en el que la letra intermedia no sea «a»:

grep -E 'J[^a]s' proof_regex.txt

Podemos incluir cualquier cantidad de caracteres en la lista. Escribimos lo siguiente para buscar nombres que comiencen con «J», terminen en «s» y contengan cualquier vocal en el medio:

grep -E 'J[aeiou]s' proof_regex.txt

Expresiones de Intervalo

Puedes usar expresiones de intervalo para especificar la cantidad de veces que deseas que el carácter o grupo anterior se encuentre en la cadena coincidente. Encierras el número entre llaves ({}).

Un número por sí solo significa específicamente ese número, pero si le sigues una coma (,), significa ese número o más. Si separas dos números con una coma (1,2), significa el rango de números desde el más pequeño hasta el más grande.

Queremos buscar nombres que comiencen con «T», estén seguidos por al menos una, pero no más de dos, vocales consecutivas y terminen en «m».

Entonces, escribimos este comando:

grep -E 'T[aeiou]{1,2}m' proof_regex.txt

Esto coincide con «Tim», «Tom» y «Team».

Si queremos buscar la secuencia «el», escribimos esto:

grep -E 'el' proof_regex.txt

Agregamos una segunda «l» al patrón de búsqueda para incluir solo secuencias que contengan doble «l»:

grep -E 'ell' proof_regex.txt

Esto es equivalente a este comando:

grep -E 'el{2}' proof_regex.txt

Si proporcionamos un rango de «al menos uno y no más de dos» ocurrencias de «l», coincidirá con secuencias «el» y «ell».

Esto es sutilmente diferente de los resultados del primero de estos cuatro comandos, en los que todas las coincidencias eran para secuencias «el», incluidas aquellas dentro de las secuencias «ell» (y solo se resalta una «l»).

Escribimos lo siguiente:

grep -E 'el{1,2}' proof_regex.txt

Para encontrar todas las secuencias de dos o más vocales, escribimos este comando:

grep -E '[aeiou]{2,}' proof_regex.txt

Caracteres de Escape

Digamos que queremos encontrar líneas en las que un punto (.) sea el último carácter. Sabemos que el signo de dólar ($) es el ancla de fin de línea, por lo que podríamos escribir esto:

grep -E '[aeiou]{2,}' proof_regex.txt

Sin embargo no obtenemos lo que esperábamos.

Como mencionamos antes, el punto (.) coincide con cualquier carácter único. Dado que cada línea termina con un carácter, todas las líneas se devuelven en los resultados.

Entonces, ¿cómo evitas que un carácter especial realice su función de regex cuando solo deseas buscar ese carácter real? Para hacer esto, usas una barra invertida (\) para escapar el carácter.

Una de las razones por las que estamos usando las opciones -E (extendidas) es porque requieren mucho menos escape cuando usas las regex básicas.

Escribimos lo siguiente:

grep -E '\.$' proof_regex.txt

Esto coincide con el carácter de punto real (.) al final de una línea.

Anclaje y Palabras

Cubrimos tanto el ancla de inicio (^) como la de fin de línea ($) anteriormente. Sin embargo, también puedes usar otras anclas para operar en los límites de las palabras.

En este contexto, una palabra es una secuencia de caracteres delimitada por espacios en blanco (el inicio o fin de una línea). Entonces, «psy66oh» se consideraría una palabra, aunque no la encontrarás en un diccionario.

El ancla de inicio de palabra es (\<); nota que apunta hacia la izquierda, al inicio de la palabra. Digamos que se escribió un nombre por error en minúsculas. Podemos usar la opción -i para realizar una búsqueda sin distinguir mayúsculas y minúsculas y encontrar nombres que comienzan con «h».

Para encuentrar todas las ocurrencias de «h», no solo las que están al comienzo de las palabras:

grep -E -i 'h' proof_regex.txt

Para encontrar solo las que están al comienzo de las palabras:

grep -E -i '\<h' proof_regex.txt

Búsqueda Similar con la Letra «y»; solo queremos ver las instancias en las que está al final de una palabra. Escribimos lo siguiente:

grep -E -i 'y' proof_regex.txt

Ahora, escribimos lo siguiente, utilizando el ancla de fin de palabra (/>) (que apunta hacia la derecha, es decir, el final de la palabra):

grep -E -i 'y\>' proof_regex.txt

Patrón de Búsqueda para Palabra Completa

Para crear un patrón de búsqueda que busque una palabra completa, puedes usar el operador de límite (\b). Usaremos el operador de límite (\B) en ambos extremos del patrón de búsqueda para encontrar una secuencia de caracteres que debe estar dentro de una palabra más grande:

grep -E -i '\bGlenn\b' proof_regex.txt
grep -E -i '\Bway\B' proof_regex.txt

Clases de Caracteres Adicionales

Puedes usar atajos para especificar las listas en las clases de caracteres. Estos indicadores de rango te ahorran tener que escribir cada miembro de una lista en el patrón de búsqueda.

Puedes usar los siguientes:

  • A-Z: Todas las letras mayúsculas de «A» a «Z».
  • a-z: Todas las letras minúsculas de «a» a «z».
  • 0-9: Todos los dígitos del cero al nueve.
  • d-p: Todas las letras minúsculas de «d» a «p». Estos estilos de formato libre te permiten definir tu propio rango.
  • 2-7: Todos los números del dos al siete.

También puedes usar tantas clases de caracteres como desees en un patrón de búsqueda. El siguiente patrón de búsqueda coincide con secuencias que comienzan con «J,» seguido por una «o» o «s,» y luego una «e,» «h,» «l,» o «s»:

grep -E 'J[os][ehls]' proof_regex.txt

En nuestro siguiente comando, usaremos el especificador de rango de a-z.

Nuestro comando de búsqueda se divide de la siguiente manera:

H: La secuencia debe comenzar con «H.»
[a-z]: El siguiente carácter puede ser cualquier letra minúscula en este rango.
*: El asterisco aquí representa cualquier número de letras minúsculas.
man: La secuencia debe terminar con «man.»

Lo juntamos todo en el siguiente comando:

grep -E 'H[a-z]*man' proof_regex.txt

Nada es Impenetrable

Algunas expresiones regulares pueden volverse difíciles de analizar visualmente. Cuando las personas escriben expresiones regulares complicadas, suelen comenzar con algo pequeño y agregar más secciones hasta que funcione. Tienden a volverse más sofisticadas con el tiempo.

Cuando intentas trabajar hacia atrás desde la versión final para ver qué hace, es un desafío completamente diferente.

Por ejemplo, mira este comando:

grep -E '^([0-9]{4}[- ]){3}[0-9]{4}|[0-9]{16}' proof_regex.txt

¿Dónde comenzarías a desenredar esto? Empezaremos desde el principio y lo tomaremos una parte a la vez:

  • ^: El ancla de inicio de línea. Por lo tanto, nuestra secuencia debe ser lo primero en una línea.
  • ([0-9]{4}[- ]): Los paréntesis agrupan los elementos del patrón de búsqueda en un grupo. Se pueden aplicar otras operaciones a este grupo en su conjunto (más sobre eso después). El primer elemento es una clase de caracteres que contiene un rango de dígitos del cero al nueve [0-9]. Nuestro primer carácter, entonces, es un dígito del cero al nueve. Luego, tenemos una expresión de intervalo que contiene el número cuatro {4}. Esto se aplica a nuestro primer carácter, que sabemos que será un dígito. Por lo tanto, la primera parte del patrón de búsqueda es ahora cuatro dígitos. Puede ir seguida de un espacio o un guión (-) de otra clase de caracteres.
  • {3}: Un especificador de intervalo que contiene el número tres sigue inmediatamente al grupo. Se aplica a todo el grupo, por lo que nuestro patrón de búsqueda ahora es de cuatro dígitos, seguido de un espacio o un guión, que se repite tres veces.
  • [0-9]: A continuación, tenemos otra clase de caracteres que contiene un rango de dígitos del cero al nueve [0-9]. Esto agrega otro carácter al patrón de búsqueda, y puede ser cualquier dígito del cero al nueve.
  • {4}: Otra expresión de intervalo que contiene el número cuatro se aplica al carácter anterior. Esto significa que ese carácter se convierte en cuatro caracteres, todos los cuales pueden ser cualquier dígito del cero al nueve.
    |: El operador de alternancia nos dice que todo a la izquierda de él es un patrón de búsqueda completo, y todo a la derecha es un nuevo patrón de búsqueda. Entonces, este comando en realidad está buscando cualquiera de dos patrones de búsqueda. El primero es tres grupos de cuatro dígitos, seguidos de un espacio o un guión, y luego se le agregan otros cuatro dígitos.
  • [0-9]: El segundo patrón de búsqueda comienza con cualquier dígito del cero al nueve.
  • {16}: Un operador de intervalo se aplica al primer carácter y lo convierte en 16 caracteres, todos los cuales son dígitos.

Por lo tanto, nuestro patrón de búsqueda buscará cualquiera de los siguientes:

  • Cuatro grupos de cuatro dígitos, cada grupo separado por un espacio o un guión (-).
  • Un grupo de 16 dígitos.

Este patrón de búsqueda busca formas comunes de escribir números de tarjetas de crédito. También es lo suficientemente versátil como para encontrar diferentes estilos, con un solo comando.

Curiosidad

Con esto detectas primos en perl:

/^1?$|^(11+?)\1+$/

Más apuntes

Invítame a un café con bitcoins:
1QESjZDPxWtZ9sj3v5tvgfFn3ks13AxWVZ

Bitcoins para café
También puedes invitarme a algo para mojar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Rellena este campo
Rellena este campo
Por favor, introduce una dirección de correo electrónico válida.
Tienes que aprobar los términos para continuar