Mejores artículos y tutoriales de Linux
Mejores artículos y tutoriales de Linux

No siempre es fácil ser efectivo de inmediato cuando profundizas por primera vez en un código base que contiene varios miles de líneas.

Pero una gran arma secreta para encontrar el camino a través de tantas líneas de código es la herramienta grep.

Voy a compartir con ustedes cómo usar el comando grep en Linux con ejemplos.

Ejemplos útiles de la vida real de los comandos grep en Linux

Si observa el man del comando grep, verás esta breve descripción de la grepherramienta: “Imprimir líneas que coincidan con un patrón”.

Sin embargo, no te dejes engañar por una definición tan humilde: grepes una de las herramientas más útiles en la caja de herramientas de Unix y hay innumerables ocasiones para poder usarla, tan pronto como trabajes con archivos de texto.

Siempre es mejor tener ejemplos del mundo real para aprender cómo funcionan las cosas.

Por tanto, usaremos el árbol de fuentes de Asciidoctor.js para ilustrar algunas de las capacidades de grep.

Puedes descargar ese árbol de fuentes de GitHub y si lo deseas, incluso puedes consultar el mismo conjunto de cambios que utilizan al escribir este artículo.

Eso te asegurará obtener resultados perfectamente idénticos a los descritos en el resto del artículo:

git clone https://github.com/asciidoctor/asciidoctor.js
cd asciidoctor.js
git checkout v1.5.6-rc.1

1. Encuentra todas las apariciones de una cadena (uso básico)

Asciidoctor.js admite el motor Nashorn JavaScript para la plataforma Java.

Si aún no conoces Nashorn, podrías aprovechar la oportunidad para aprender más al explorar las partes del proyecto que hacen referencia a ese motor de JavaScript.

Como punto de partida, puedes comprobar si hay alguna configuración relacionada con Nashorn en el archivo package.json que describe las dependencias del proyecto:

rosepac@ciberninjas:~$ grep nashorn package.json
    "test": "node npm/test/builder.js && node npm/test/unsupported-features.js && node npm/test/jasmine-browser.js && node npm/test/jasmine-browser-min.js && node npm/test/jasmine-node.js && node npm/test/jasmine-webpack.js && npm run test:karmaBrowserify && npm run test:karmaRequirejs && node npm/test/nashorn.js",

Sí, aparentemente hubo algunas pruebas específicas de Nashorn. Entonces, investiguemos eso un poco más.

2. Búsqueda insensible a mayúsculas y minúsculas en un conjunto de archivos

Ahora, echemos un vistazo más de cerca a los archivos del directorio ./npm/test/ que menciona explícitamente a Nashorn.

Una búsqueda que no distingue entre mayúsculas y minúsculas (usa la opción -i) probablemente sea mejor aquí, ya que necesitamos encontrar ambas referencias a nashorn o Nashorn(cualquier otra combinación de caracteres en mayúsculas y minúsculas):

rosepac@ciberninjas:~$ grep -i nashorn npm/test/*.js
npm/test/nashorn.js:const nashornModule = require('../module/nashorn');
npm/test/nashorn.js:log.task('Nashorn');
npm/test/nashorn.js:nashornModule.nashornRun('jdk1.8.0');

La insensibilidad a mayúsculas y minúsculas puede ser útil en muchas ocasiones. De lo contrario, se perderían algunas búsquedas, como por ejemplo la declaración require('../module/nashorn').

3. Encuentra todos los archivos que no coincidan

¿Hay algunos archivos específicos que no sean de Nashorm en el directorio npm/test/?

Si quieres responder a esa pregunta, podemos usar la opción de “imprimir archivos que no coinciden” mediante el comando grep (opción -L):

$ grep -iL nashorn npm/test/*
npm/test/builder.js
npm/test/jasmine-browser-min.js
npm/test/jasmine-browser.js
npm/test/jasmine-node.js
npm/test/jasmine-webpack.js
npm/test/unsupported-features.js

Observa cómo con la opción de salida -L de grep cambia para mostrar solo los nombres de archivo.

Por lo tanto, no se muestra ninguno de los archivos anteriores que contienen la cadena “nashorn” (independientemente del caso).

Eso no quiere decir que no estén relacionados de alguna manera con esa tecnología, pero al menos, las letras “nashorn” no están presentes en la búsqueda.

4. Encontrar patrones en archivos ocultos y recursivamente en subdirectorios

Los últimos dos comandos usaron un patrón global de shell para pasar la lista de archivos para examinar al comando grep.

Sin embargo, esto tiene algunas limitaciones inherentes: El comodín ( *) no coincidirá con los archivos ocultos y tampoco lo hará con los archivos (eventualmente) contenidos en los subdirectorios.

Una solución en este caso, sería combinar grepcon el comando de búsqueda en lugar de confiar en un patrón global de shell:

# This is not efficient as it will spawn a new grep process for each file
rosepac@ciberninjas:~$ find npm/test/ -type f -exec grep -iL nashorn \{} \;
# This may have issues with filenames containing space-like characters
rosepac@ciberninjas:~$ grep -iL nashorn $(find npm/test/ -type f)

Como se menciona en los comentarios del bloque de código anterior, cada solución tiene algunos inconvenientes.

Con respecto a los nombres de archivo que contienen caracteres similares a espacios, debes investigar la opción de grep -z capaz de combinar con la opción -print0 del comando find, así puede mitigar algunos problemas.

Sin embargo, una mejor solución sería usar la opción “recursiva” de grep. Con esa opción, se proporciona en la línea de comando la raíz de tu árbol de búsqueda (el directorio de inicio) en lugar de la lista explícita de nombres de archivo para examinar.

Con la opción -r, grep buscará todos los archivos en el directorio especificado; incluidos los archivos ocultos y luego rastreará recursivamente cualquier subdirectorio:

rosepac@ciberninjas:~$ grep -irL nashorn npm/test/npm/
npm/test/builder.js
npm/test/jasmine-browser-min.js
npm/test/jasmine-browser.js
npm/test/jasmine-node.js
npm/test/jasmine-webpack.js
npm/test/unsupported-features.js

En realidad, con esa opción, también podríamos comenzar la exploración un nivel más arriba para ver si existen pruebas de que no son npm que también se dirigen a Nashorn:

rosepac@ciberninjas:~$ grep -irL nashorn npm/

Puedes probar ese comando por tu propia cuenta; como pista, ¡puedo decirte que debería encontrar muchos más archivos coincidentes!

5. Filtrado de archivos por su nombre (usando expresiones regulares)

Bien, parece haber algunas pruebas específicas de Nashorn en el proyecto y dado que Nashorn es Java, otra pregunta que podría plantearse sería:

“¿Existen algunos archivos fuente de Java en el proyecto que menciona explícitamente a Nashorn?” .

Dependiendo de la versión de grepque utilices, hay al menos dos soluciones para poder responder a esa pregunta.

El primero, sería utilizar grep para encontrar todos los archivos que contienen el patrón “nashorn” y luego canalizar la salida de ese primer comando a una segunda instancia de grep capaz de filtrar los archivos de origen que no son de Java.

Sería algo así:

rosepac@ciberninjas:~$ grep -ir nashorn ./ | grep "^[^:]*\.java"
./spec/nashorn/AsciidoctorConvertWithNashorn.java:public class AsciidoctorConvertWithNashorn {
./spec/nashorn/AsciidoctorConvertWithNashorn.java:    ScriptEngine engine = engineManager.getEngineByName("nashorn");
./spec/nashorn/AsciidoctorConvertWithNashorn.java:    engine.eval(new FileReader("./spec/nashorn/asciidoctor-convert.js"));
./spec/nashorn/BasicJavascriptWithNashorn.java:public class BasicJavascriptWithNashorn {
./spec/nashorn/BasicJavascriptWithNashorn.java:    ScriptEngine engine = engineManager.getEngineByName("nashorn");
./spec/nashorn/BasicJavascriptWithNashorn.java:    engine.eval(new FileReader("./spec/nashorn/basic.js"));

La primera mitad del comando ya debería ser comprensible para ti. Pero, ¿Qué pasa con esa última parte “^[\^:]*\.java”?

A menos que se especifique la opción -F, grepasume que el patrón de búsqueda es una expresión regular.

Eso significa que, además de los caracteres simples que coincidirán literalmente, tiene acceso a un conjunto de meta caracteres para describir patrones más complejos.

El patrón que se uso arriba solamente coincidirá:

  • ^ el comienzo de la linea
  • [^:]* seguido de una secuencia de cualquier carácter excepto dos puntos
  • \. seguido de un punto (el punto tiene un significado especial en las expresiones regulares , por lo que se debe proteger con el carácter de escape -barra invertida- para poder expresar que se quiere una coincidencia literal)
  • java seguido de las cuatro letras “java”.

En la práctica, dado grep que usa dos puntos para separar el nombre del archivo del contexto, se deben mantener solo las líneas que tienen .java en la sección del nombre del archivo.

Vale la pena mencionar, que esa búsqueda también coincidiría con los nombres de archivo .javascript.

6. Filtrado de archivos por su nombre usando grep

Las expresiones regulares son extremadamente poderosas. Sin embargo, en este caso particular, parece excesivo.

Sin mencionar que con la solución anterior, dedicamos tiempo a examinar todos los archivos en busca del patrón “nashorn”, la mayoría de los resultados se descartan en el segundo paso de la canalización.

Si estás utilizando la versión GNU de grep, algo que es probable si estás utilizando Linux, tienes otra solución posible incluyendo la opción --include.

Eso indica a grep que busque solamente en archivos cuyo nombre coincida con el patrón global dado:

rosepac@ciberninjas:~$ grep -ir nashorn ./ --include='*.java'
./spec/nashorn/AsciidoctorConvertWithNashorn.java:public class AsciidoctorConvertWithNashorn {
./spec/nashorn/AsciidoctorConvertWithNashorn.java:    ScriptEngine engine = engineManager.getEngineByName("nashorn");
./spec/nashorn/AsciidoctorConvertWithNashorn.java:    engine.eval(new FileReader("./spec/nashorn/asciidoctor-convert.js"));
./spec/nashorn/BasicJavascriptWithNashorn.java:public class BasicJavascriptWithNashorn {
./spec/nashorn/BasicJavascriptWithNashorn.java:    ScriptEngine engine = engineManager.getEngineByName("nashorn");
./spec/nashorn/BasicJavascriptWithNashorn.java:    engine.eval(new FileReader("./spec/nashorn/basic.js"));

7. Encontrar palabras

Lo interesante del proyecto Asciidoctor.js es que es un proyecto multilenguaje.

En esencia, Asciidoctor está escrito en Ruby; por lo que, para que se pueda utilizar en el mundo de JavaScript, debes “transpilarlo” utilizandoOpal, un compilador de fuente a fuente de Ruby a JavaScript.

Entonces, después de haber examinado las especificidades de Nashorn, podemos intentar comprender algo mejor la API de Opal.

Como primer paso en esa búsqueda, podemos buscar todas las menciones del objeto global Opal en los archivos JavaScript del proyecto.

Una expresión regular haría el truco. Sin embargo, una vez más, grep tiene una solución más ligera para resolver ese caso de uso común.

Usando la opción -w se harán coincidir solamente con palabras, es decir, patrones precedidos y seguidos por un carácter que no es una palabra.

Un carácter que no es una palabra es el comienzo de la línea, el final de la línea o cualquier carácter que no sea ni una letra, ni un dígito, ni un guion bajo:

rosepac@ciberninjas:~$ grep -irw --include='*.js' Opal .
...

8. Colorear la salida

No copie la salida del comando anterior ya que existen muchas coincidencias.

Cuando la salida es tan densa, es posible que desees agregar un poco de color para facilitar la comprensión de los resultados.

Si eso aún no está configurado de forma predeterminada en tu sistema, puedes activar esa característica usando la opción GNU --color:

rosepac@ciberninjas:~$ grep -irw --color=auto --include='*.js' Opal .
...

Deberías obtener el mismo resultado largo que antes, pero esta vez la cadena de búsqueda debería aparecer coloreada.

9. Contar líneas coincidentes o archivos coincidentes

En los casos de que las salidas de los comandos sea tan larga, puedes tomar la opción de conocer cuánto de largas son.

rosepac@ciberninjas:~$ grep -irw --include='*.js' Opal . | wc -l
86

Eso significa que tenemos un total de 86 líneas coincidentes en todos los archivos examinados.

Sin embargo, ¿Cuántos archivos diferentes coinciden? Con la opción -l puedes limitar la salida de grep en los archivos coincidentes en lugar de mostrar las líneas coincidentes.

Entonces, con ese simple cambio podrás conocer cuántos archivos coinciden con la búsqueda deseada:

rosepac@ciberninjas:~$ grep -irwl --include='*.js' Opal . | wc -l
20

Si eso te recuerda a la opción de -L no te sorprendas: Como es relativamente común, se usan mayúsculas y minúsculas para distinguir opciones complementarias.

El atributo -lmuestra los nombres de archivo coincidentes. -L muestra nombres de archivos que no coinciden.

Además, puedes consultar en el manual otras opciones, como por ejemplo la opción de -h/ .-H

Cerremos ese paréntesis y volvamos a nuestros resultados: 86 líneas coincidentes. 20 archivos coincidentes. Sin embargo, ¿cómo se distribuyen las líneas coincidentes en los archivos coincidentes?

Podemos saber que al usar la opción -c de grep eso contará el número de líneas coincidentes por archivo examinados (incluidos los archivos con cero coincidencias):

rosepac@ciberninjas:~$ grep -irwc --include='*.js' Opal .
...

A menudo, esa salida necesita un procesamiento posterior, ya que muestra sus resultados en el orden en que se examinaron los archivos y también incluye archivos sin ninguna coincidencia; algo que generalmente no nos interesa.

Eso podemos resolverlo tal que así:

rosepac@ciberninjas:~$ grep -irwc --include='*.js' Opal . | grep -v ':0$'

En cuanto a ordenar la búsqueda, puedes agregar el comando de ordenar al final de la canalización:

rosepac@ciberninjas:~$ grep -irwc --include='*.js' Opal . | grep -v ':0$' | sort -t: -k2n

No dudes en consultar en el manual el manejo del comando sort para conocer el significado exacto de las opciones que puedes utilizar.

10. Encontrar la diferencia entre dos conjuntos coincidentes

Si recuerdas, hace algunos comandos, busqué la palabra “Opal”.

Sin embargo, si buscas en el mismo conjunto de archivos todas las apariciones de la cadena “Opal”, obtendrás unas veinte respuestas más:

rosepac@ciberninjas:~$ grep -irw --include='*.js' Opal . | wc -l
86
rosepac@ciberninjas:~$ grep -ir --include='*.js' Opal . | wc -l
105

El conocer la diferencia entre esos dos conjuntos sería interesante. Entonces, ¿Cuáles son las líneas que contienen las cuatro letras “opal” en una fila, pero donde esas cuatro letras no forman una palabra completa?

Esto no es muy fácil de ejecutar, porque la misma línea puede contener tanto la palabra Opal como una palabra más grande que contenga esas cuatro letras.

Pero como primera aproximación, puedes usar esta canalización:

rosepac@ciberninjas:~$ grep -ir --include='*.js' Opal . | grep -ivw Opal
./npm/examples.js:  const opalBuilder = OpalBuilder.create();
./npm/examples.js:  opalBuilder.appendPaths('build/asciidoctor/lib');
./npm/examples.js:  opalBuilder.appendPaths('lib');
...

Conclusión final

Por supuesto, no entenderás la organización de un proyecto y mucho menos la arquitectura del código con solo realizar algunas búsquedas, ¡Solamente estamos trabajando un par de pruebas del comando grep!

Sin embargo, el comando de grep es inevitable a la hora de identificar puntos de referencia y puntos de partida mientras se explora una nueva base de código.

Entonces, espero que este artículo te ayude a comprender el poder del comando grep y que lo agregues a tu caja de herramientas, ¡Sin lugar a dudas, no te vas a arrepentir!

Compartir es Vivir!

Comparte tu opinión