Pipelines básicos

Portada del post Pipelines básicos
Avatar de Antitesista
Antitesista

Introducción

Supongamos que quieres cocinar papas fritas. Si trataras de prepararlas sin poder usar pipelines, tendrías que seguir el siguiente proceso:

  1. Lavar las papas y guardarlas en un cajón

  2. Sacarlas del cajón, pelarlas y guardarlas en otro cajón

  3. Sacarlas del cajón, cortarlas y guardarlas en otro cajón

  4. Sacarlas del cajón, ponerlas a freír, sacarlas y guardarlas en otro cajón

  5. Sacarlas del cajón, ponerles sal y guardarlas en otro cajón

  6. Sacarlas del cajón y comerlas

Si he hecho bien mi trabajo, se puede notar algo bastante curioso: tengo que guardar las papas en algún lado luego de realizar cualquier acción con ellas. No obstante, naturalmente, nadie haría eso en la jamás. Lo que se esperaría de nuestra realidad sería que pueda usar el producto que se obtiene en cada paso directamente en el paso siguiente. Eso es exactamente lo que nos permiten hacer los pipelines en una shell: tener un flujo más natural de información.

Si has visto | en un comando y no tienes idea de qué hace, estás en el lugar indicado (según yo). Ese símbolo se llama pipe y permite redirigir la salida de un comando a otro para poder hacer operaciones complejas.

La forma común en la que se usan es más o menos como esta:

1command1 | command2 | command3 | command4 #...

El flujo exacto será explicado más adelante. No obstante, desde ya se puede ver que es una sintaxis bastante intuitiva y limpia. Da la idea de que los comandos siguen una línea uno luego de otro. Esta es la razón del nombre, porque pipe significa tubería. Así que podemos entender que, a partir de un comando principal, por donde entran datos, vamos pasándolos por una tubería, lugar en el que serán tratados de distintas maneras por distintos comandos. Todo esto para que obtengamos lo que queremos al final de la tubería: un output limpio.

Ejemplos

Filtrar la salida con grep

Los pipes son muy usados en diversos contextos. Uno de los más comunes va de usar grep para poder filtrar la salida de un comando dado.

Pongámonos en un escenario en el que yo tenga un archivo de texto de varias líneas. Si quiero ver el contenido en mi terminal, podría usar el comando cat:

1$ cat texto 2The Zen of Python, by Tim Peters 3 4Beautiful is better than ugly. 5Explicit is better than implicit. 6Simple is better than complex. 7Complex is better than complicated. 8Flat is better than nested. 9Sparse is better than dense. 10Readability counts. 11Special cases aren't special enough to break the rules. 12Although practicality beats purity. 13Errors should never pass silently. 14Unless explicitly silenced. 15In the face of ambiguity, refuse the temptation to guess. 16There should be one-- and preferably only one --obvious way to do it. 17Although that way may not be obvious at first unless you're Dutch. 18Now is better than never. 19Although never is often better than *right* now. 20If the implementation is hard to explain, it's a bad idea. 21If the implementation is easy to explain, it may be a good idea. 22Namespaces are one honking great idea -- let's do more of those!

Si yo quisiera buscar todas las veces en las que se menciona la palabra better, sería un poco complicado leer todo el texto. Para facilitarme el trabajo, puedo filtrar la salida con el comando grep de la siguiente manera:

1$ cat texto | grep better 2Beautiful is better than ugly. 3Explicit is better than implicit. 4Simple is better than complex. 5Complex is better than complicated. 6Flat is better than nested. 7Sparse is better than dense. 8Now is better than never. 9Although never is often better than *right* now.

Lo que he hecho ha sido simplemente tomar la salida del primer comando (cat texto) y pasarla como la entrada de grep. Este último permite filtrar todas las líneas de acuerdo a un patrón que yo defina. Aquí, el patrón es que se deben filtrar todas las líneas que contengan better.

Salida interactiva con less

Si nos ponemos en el ejemplo del archivo de texto nuevamente, podremos entender este caso de uso de una forma también sencilla.

Si el archivo de texto tiene muchas líneas, es un poco complicado desplazarnos por la terminal desde el inicio del archivo. Tendríamos que hacer scroll de forma muy constante y eso es un poco molesto jajajaja

Afortunadamente, el comando less permite que nos desplacemos por el texto de entrada de forma interactiva. Como ya vimos qué pasaba si escribíamos cat texto, ahora veamos qué sucede si escribimos el siguiente comando

1cat texto | less

El resultado sería el siguiente:

1The Zen of Python, by Tim Peters 2 3Beautiful is better than ugly. 4Explicit is better than implicit. 5Simple is better than complex. 6Complex is better than complicated. 7Flat is better than nested. 8Sparse is better than dense. 9Readability counts. 10Special cases aren't special enough to break the rules. 11Although practicality beats purity. 12Errors should never pass silently. 13Unless explicitly silenced. 14In the face of ambiguity, refuse the temptation to guess. 15:

Podemos ver que el formato es distinto a la última vez. De hecho, en la salida no se puede ver el comando que hemos introducido. Esto se debe a que la terminal ha sido capturada por el buffer de less. En esta nueva pantalla, podemos movernos verticalmente haciendo scroll con el ratón (a veces) y con las teclas (arriba: flecha o k; abajo: flecha o j). Para salir podemos presionar la tecla q.

Hay que notar que, casualmente, el comando less puede tomar como parámetro el archivo de texto directamente, así que estas dos líneas son básicamente equivalentes:

1cat texto | less 2less texto

No obstante, sí se puede apreciar el verdadero poder de less y los pipes si, por ejemplo, estamos en una carpeta con muchísimos archivos o usamos algún comando con una salida más grande. Si usáramos ls en un directorio muy grande, estaríamos en un escenario similar al que acabamos de ver, así que tendríamos que usar un pipeline (esto lo puedes probar en tu shell, solo reemplaza mi-directorio-grande por un directorio grande xd).

1ls mi-directorio-grande | less

Redirección múltiple de salida con tee

Con el primer ejemplo podemos hacer nuestro primer pipeline de tres partes. Imaginemos que queremos guardar todas las líneas del archivo texto que contienen better en un nuevo archivo llamado solo-better. Para eso, podríamos usar la redirección de flujo (tema de otro post), de la siguiente manera:

1cat texto | grep better > solo-better

Si yo corro ese comando, obtengo el resultado fácilmente, pero no tengo output (la redirección de flujo se hace con > y manda la salida del comando cat texto | grep better al archivo solo-better).

Para ver el resultado de lo que he ejecutado, tendría que hacer cat solo-better. Esto es un poco trabajoso, puesto que tengo que esperar que se termine de realizar toda la operación para poder ver el resultado que ha sido guardado en un archivo.

Afortunadamente, tenemos al dios tee. Este comando nos permite guardar el texto en un archivo y mostrarlo en la pantalla al mismo tiempo (justo lo que buscamos). Podríamos usarlo de la siguiente manera:

1$ cat texto | grep better | tee solo-better 2Beautiful is better than ugly. 3Explicit is better than implicit. 4Simple is better than complex. 5Complex is better than complicated. 6Flat is better than nested. 7Sparse is better than dense. 8Now is better than never. 9Although never is often better than *right* now.

Combinando todo lo anterior

Basándonos en lo que he repetido varias veces, esto se ve bastante bien si se trata de archivos pequeños, pero, si tuviera muchas líneas, podría ser un poco engorroso usar el comando tee para ver toda la salida. Si el output filtrado tuviera unas 100 líneas, sería más conveniente poder hacer scroll directamente en la terminal, como lo que nos permite hacer el comando less, así que podemos integrarlo a nuestro pipeline de la siguiente manera:

1cat texto | grep better | tee solo-better | less

Con el comando anterior, hacemos lo siguiente:

  1. Obtenemos todo el contenido del archivo texto.

  2. Filtramos ese contenido para obtener solo las líneas que contienen better.

  3. Guardamos la salida en el archivo solo-better.

  4. Mostramos la misma salida de forma interactiva en la shell.

En el siguiente post de pipelines, podremos hacer operaciones un poco más complejas.

Hasta la vista xd


Más publicaciones