Cómo usar el comando grep en GNU/Linux

En este tutorial, cubriremos cómo usar grep, como una herramienta de línea de comandos. Cubriremos sus opciones, así como ejemplos prácticos y casos de uso.

Introducción

Grep es una herramienta poderosa pero muy simple. De forma predeterminada, busca a través de una entrada e imprime una o varias líneas que contienen texto que coincide con un patrón especificado en la llamada de comando.

Antes de que grep se convirtiera en una herramienta tan extendida para el sistema GNU/Linux, solía ser una utilidad privada escrita por ken thompson para buscar archivos. La parte interesante de la historia es que su gerente se acercó a él y le pidió una herramienta que hiciera exactamente eso.

Respondió que pensaría en algo de la noche a la mañana, mientras que en realidad usó ese tiempo para mejorar el código y corregir algunos errores. Cuando presentó la herramienta al día siguiente, realmente parecía que la había escrito en poco tiempo.

En este artículo, aprenderemos los conceptos básicos de grep y su uso al analizar sus opciones y algunos ejemplos.

Uso y variantes de Grep

La forma general del comando grep es:

1
$ grep [OPTION...] [PATTERNS] [FILE...]

Podemos especificar cero o más argumentos OPCIÓN, uno o más PATRONES y cero o más argumentos ARCHIVO. Si no se especifica el argumento FILE, grep busca en el directorio de trabajo (.), si se da la opción de recursividad; de lo contrario, grep busca en la tubería de entrada estándar.

Hay cuatro variantes principales de grep. Dependiendo de cuál se adapte mejor a nuestras necesidades, elegimos el que usaremos especificando el argumento OPCIÓN:

  • -G o --basic-regexp - Cuando se usa, interpreta el patrón como una [expresión regular básica](https://en.wikibooks.org/wiki/Regular_Expressions/ POSIX_Basic_Regular_Expressions) (BRE). Esta variante se usa por defecto (si no se especifican otras opciones).
  • -E o --extended-regexp - Interpreta el patrón como una Expresión regular extendida (ERE ).
  • -F o --fixed-strings - Interpreta el patrón como cadenas fijas, no como expresiones regulares.
  • -P o --perl-regexp - Interpreta el patrón como Expresiones regulares compatibles con Perl (PCREs). Esta variante todavía tiene algunas características no implementadas y podría generar advertencias. Debe considerarse bastante experimental cuando se usa con ciertas opciones y se recomienda solo para usuarios avanzados.

Lo que vale la pena señalar es que hoy en día, grep es una familia de herramientas, que incluye egrep, fgrep y rgrep. Son lo mismo que grep -E, grep -F y grep -R respectivamente, pero están en desuso como herramientas independientes y solo se proporcionan porque algunos programas aún dependen de ellas.

En nuestros ejemplos, usaremos la segunda variante, aunque los ejemplos de las secciones anteriores también deberían aplicarse a otras variantes.

Búsqueda en un archivo

Digamos que tenemos un archivo test.txt, con los siguientes contenidos:

1
2
3
4
5
6
7
hello 
hElLo
This line does not include the word we're looking for. 
helloHello
  This is the paragraph that has multiple sentences. We'll put one more hello here.
Test line.
Another hello line.

Nos gustaría encontrar todas las líneas que contengan la palabra hola. No importa dónde se encuentra la palabra en la línea, ni si es parte de una palabra más larga como holaHola.

Usaremos grep -E con el patrón hello, en el archivo test.txt:

1
$ grep -E hello test.txt

Ejecutar este comando produciría:

1
2
3
4
hello 
helloHello
  This is the paragraph that has multiple sentences. We'll put one more hello here.
Another hello line.

Nota: Es una práctica común poner el patrón entre comillas para separarlo visualmente como un patrón. Esto también nos permite poner varias palabras como término de búsqueda, en lugar de solo una.

Búsqueda de varias palabras

A veces, nos gustaría buscar un par de palabras en lugar de una. Esto se hace simplemente incluyendo los términos de búsqueda entre comillas:

1
$ grep -E "This is" test.txt

Ejecutar esto resultaría en:

1
This is the paragraph that has multiple sentences. We'll put one more hello here.

Tenga en cuenta que grep distingue entre mayúsculas y minúsculas. Si hubiéramos buscado "esto es" en su lugar, nada devolvería.

Usar expresiones regulares

Ahora, usemos una expresión regular para destacar la palabra hola y omitir resultados como holaHola:

1
$ grep -E "(\s|[^a-zA-Z0-9_]*)hello\s" test.txt

Ejecutar este comando dará como resultado:

1
2
3
hello 
This is the paragraph that has multiple sentences. We'll put one more hello here.
Another hello line.

Especificación de reglas de coincidencia con indicadores

Hay algunas opciones que nos ayudan a especificar las reglas de coincidencia más fácilmente:

  • -patrón e

Esta bandera significa que la cadena anterior debe interpretarse como un patrón. De forma predeterminada, si tiene un patrón, no es necesario marcarlo con -e. Si tiene varios patrones, tendrá que marcarlos todos.

Por ejemplo, podemos buscar múltiples patrones así:

1
$ grep -E -e "hello" -e "the" test.txt

Este comando dará como resultado:

1
2
3
4
5
hello 
This line does not include the word we're looking for. 
helloHello
This is the paragraph that has multiple sentences. We'll put one more hello here.
Another hello line.
  • -archivo f

Esta bandera nos permite obtener patrones de un archivo, uno por línea. Cuando estamos tratando con muchos patrones, es más fácil ubicarlos en un archivo en lugar de tenerlos todos dentro de la línea de comandos. Cada patrón presente en el archivo se tiene en cuenta:

1
$ grep -E -f patterns.txt test.txt

Así es como se ve nuestro archivo patterns.txt:

1
2
^helloHello$
*line*

Ejecutar el comando dará como resultado:

1
2
3
4
This line does not include the word we're looking for. 
helloHello
Test line.
Another hello line.
  • -i o --ignore-case

Esta marca anula el comportamiento predeterminado que distingue entre mayúsculas y minúsculas y devuelve todos los patrones coincidentes, independientemente de las mayúsculas y minúsculas:

1
$ grep -E -i "hello" test.txt

Ejecutar este comando dará como resultado:

1
2
3
4
5
hello 
hElLo
helloHello
This is the paragraph that has multiple sentences. We'll put one more hello here.
Another hello line.

Esta vez, a pesar de que nuestro término de búsqueda era "hola", se devolvieron otras líneas relevantes, como helLo.

Nota: -y es una versión obsoleta de -i que hace lo mismo, pero se mantiene solo por compatibilidad con versiones anteriores.

  • -v o --invert-match

Invierte la coincidencia: devuelve las líneas que no coinciden con nuestro patrón:

1
$ grep -E -v "hello" test.txt

Ejecutar este comando daría como resultado:

1
2
3
hElLo
This line does not include the word we're looking for. 
Test line.
  • -w o --word-regexp

Busca palabras "independientes", antes y después de las cuales encontrará un espacio, una nueva línea, un tabulador, etc. Si están al principio o al final de la línea, los constituyentes anteriores y posteriores que no son palabras, ya que los números, caracteres o guiones bajos no importan. Es una bandera de conveniencia abreviada para evitar escribir la expresión regular de antes:

1
$ grep -E -w "hello" test.txt

El resultado será el mismo que si simplemente hubiéramos ingresado la expresión regular para palabras independientes:

1
2
3
hello 
This is the paragraph that has multiple sentences. We'll put one more hello here.
Another hello line.
  • -x o --line-regexp

Busca líneas que coincidan con todo el patrón. Es decir, si el patrón y toda la línea coinciden, se devuelve la línea:

1
$ grep -E -x "helloHello" test.txt 

Es una bandera de conveniencia que nos permite omitir escribir la expresión regular:

1
$ grep -E "^helloHello$" test.txt 

Estos tendrán el mismo resultado; en nuestro caso, una sola línea que contiene exactamente lo que especificamos:

1
helloHello

Salida de control {#salida de control}

A veces, la información resultante puede ser desordenada. Si estamos trabajando con archivos grandes o si no queremos ver visualmente todos los resultados, nos gustaría controlar la salida y cambiar el comportamiento. Afortunadamente, grep admite una serie de banderas y opciones para hacer precisamente eso:

  • -c o --cuenta

Suprime la salida y devuelve el número de líneas coincidentes:

1
$ grep -E -c "hello" test.txt 

Este comando dará como resultado un solo número:

1
4
  • -l

Suprime la salida básica y solo muestra los nombres de los archivos que tienen líneas coincidentes. Por el bien de este ejemplo, agregaremos 4 archivos más en nuestro directorio. Dos de ellos coinciden y los otros dos no coinciden con el término de búsqueda:

1
$ grep -E -l "hello" *.txt

La última cadena (*.txt) en nuestro comando le dice al programa que busque todos los archivos en el directorio actual que tienen una extensión .txt. También podríamos enumerarlos uno por uno, pero de esta manera es más ordenado.

Este comando daría como resultado:

1
2
3
01_contains_hello.txt
02_contains_hello.txt
test.txt

Por el contrario, la opción -L muestra los nombres de los archivos que no tienen coincidencias.

  • -m hace

Detiene la búsqueda después de num líneas encontradas. Al buscar en varios archivos, este número no se transfiere de un archivo a otro y el recuento comienza de nuevo para cada uno:

1
$ grep -E -m 2 "hello" test.txt

Este comando daría como resultado:

1
2
hello 
helloHello
  • -h

Suprime los nombres de los archivos en los que se encontraron coincidencias, dado que buscamos en varios archivos. Primero busquemos regularmente:

1
$ grep -E -m 1 "hello" *.txt

Esto resulta en:

1
2
3
01_contains_hello.txt:hello
02_contains_hello.txt:hello
test.txt:hello 

Sin embargo, cuando agregamos el indicador -h:

1
$ grep -E -h -m 1 "hello" *.txt

La salida es:

1
2
3
hello
hello
hello
  • -n

Muestra el número de línea para cada línea coincidente:

1
$ grep -E -n "hello" test.txt

Esto resulta en:

1
2
3
4
1:hello 
4:helloHello
5: This is the paragraph that has multiple sentences. We'll put one more hello here.
7:Another hello line.

Uso práctico

Repasemos algunos ejemplos de uso práctico del comando grep, teniendo en cuenta las secciones anteriores.

Buscar por extensión de archivo usando Grep

Grep puede buscar a través de cualquier entrada dada. Esa entrada puede ser la tubería de entrada estándar, un archivo específico o incluso una salida de otro programa ejecutado previamente.

Aunque hay muchas formas de listar los archivos por extensión, grep puede facilitarnos la tarea.

Aquí usaremos grep para filtrar la salida de otra herramienta que enumera recursivamente todos los archivos en el directorio de trabajo actual. Intentaremos encontrar todos los archivos con una extensión .txt y ordenarlos alfanuméricamente mientras ignoramos las diferencias entre mayúsculas y minúsculas:

1
$ find . | sort -f | grep -E "*.txt"

Tenga en cuenta que en este comando usamos |, o el llamado tubo. Lo usamos para redirigir la salida de un programa a la entrada de otro, en lugar de imprimirlo en la salida estándar como lo hacemos normalmente.

Supongamos que tenemos los siguientes archivos en nuestro directorio de trabajo actual:

1
2
3
4
5
6
7
8
./some_file1.txt
./05_contains_hello.txt
./subfolder
./subfolder/py_code.py
./subfolder/some_file.txt
./c_code.c
./markdown_file.md
./some_file2.txt

La salida de nuestro comando será:

1
2
3
4
./05_contains_hello.txt
./some_file1.txt
./some_file2.txt
./subfolder/some_file.txt

Procesamiento de contenido de archivos usando Grep

Aprovecharemos esta oportunidad para mostrar otra opción ordenada de grep. Supongamos que tenemos un directorio de proyecto con una gran cantidad de archivos con código y sin acceso a un IDE elegante.

Queremos refactorizar alguna función, por lo que será muy útil para nosotros encontrar todos sus usos de antemano, para que no rompamos todo el código base.

En nuestra solución, usaremos -r, una opción grep para la búsqueda recursiva de todos los archivos contenidos en el directorio de trabajo actual y todos sus subdirectorios:

1
$ grep -E -rn "osCreateMemoryBlock\([^)]*(\s*|\))"

Tenga en cuenta que el uso de una expresión regular intermedia nos ayuda a ser más precisos en nuestra búsqueda: incluso hemos tenido en cuenta casos en los que los argumentos de la función se escriben en líneas nuevas.

Aquí, hemos combinado dos opciones: -r y -n en un solo -rn. Esto se debe a que querremos saber en qué líneas se deben realizar los cambios.

Así es como se ve el directorio de trabajo para este comando:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
./subfolder2
./subfolder2/c_code.c
./subfolder2/memory_admin.c
./log_client.c
./signals.c
./log_server.c
./fifo_server.c
./fifo_client.c
./skelet.c
./subfolder1
./subfolder1/memory_handler.c
./subfolder1/random_code1.c
./subfolder1/c_code.c
./subfolder1/memory_creater.c
./shared_memory_writer.c
./shared_memory_reader.c

Ejecutar el comando generará la ruta relativa de cada archivo y el número de cada línea que contiene osCreateMemoryBlock:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
subfolder2/c_code.c:47:void *osCreateMemoryBlock(const char* filePath, unsigned size);
subfolder2/c_code.c:116:void *osCreateMemoryBlock(const char* filePath,
subfolder2/memory_admin.c:54:   osMemoryBlock* pMsgBuf = osCreateMemoryBlock(argv[1], sizeof(osMemoryBlock));
subfolder2/memory_admin.c:116:void *osCreateMemoryBlock(const char* filePath,
log_server.c:116:void *osCreateMemoryBlock(const char* filePath,
subfolder1/memory_handler.c:54: osMemoryBlock* pMsgBuf = osCreateMemoryBlock(argv[1], sizeof(osMemoryBlock));
subfolder1/memory_handler.c:116:void *osCreateMemoryBlock(const char* filePath,
subfolder1/memory_creater.c:47:void *osCreateMemoryBlock(const char* filePath, unsigned size);
subfolder1/memory_creater.c:54: osMemoryBlock* pMsgBuf = osCreateMemoryBlock(argv[1], sizeof(osMemoryBlock));
shared_memory_writer.c:33:      int *niz = osCreateMemoryBlock(argv[1], size);

Extracción de datos del archivo de registro mediante Grep

Digamos que notó que su máquina se cuelga demasiado tiempo antes de apagarse, pero no está seguro de por qué. Es posible que desee verificar el registro de su sistema, aunque contiene millones de líneas. Si tenemos una idea aproximada de cuándo sucedió esto, grep puede ayudarnos a encontrar esta información fácilmente:

1
$ grep -E -n "May 27 19:2[0-1]:[0-9]{2} *" /var/log/syslog

Esto generará todo lo escrito en los registros del sistema durante el período que hemos especificado. También imprimirá los números de las líneas para nuestra conveniencia si deseamos explorar el archivo completo más adelante.

Naturalmente, este archivo de registro es diferente para todos, pero podría tener un aspecto similar a:

1
2
3
4
1005:May 27 19:21:59 machine kernel: [236041.840122] mce: CPU0: Core temperature above threshold, cpu clock throttled (total events = 2918)
1006:May 27 19:21:59 machine kernel: [236041.840125] mce: CPU0: Package temperature above threshold, cpu clock throttled (total events = 3753)
1014:May 27 19:21:59 machine kernel: [236041.841137] mce: CPU4: Core temperature/speed normal
1016:May 27 19:21:59 machine kernel: [236041.841139] mce: CPU4: Package temperature/speed normal

Este método se puede aplicar a prácticamente cualquier tipo de archivo de registro; solo asegúrese de verificar su formato de antemano y luego proceda a construir una expresión regular precisa.

Conclusión

En este artículo cubrimos los conceptos básicos de una de las herramientas de línea de comandos más famosas disponibles. Es una forma muy eficiente de buscar a través de cualquier texto o extraer datos de él.

A través de ejemplos de su uso en la vida real, es evidente que grep puede hacer maravillas en muchos campos de interés cuando se combina con otras herramientas de línea de comandos y conocimientos avanzados sobre la construcción de expresiones regulares.