Módulo 3: Tuberías, redirección y composición

5. Tuberías: conectar la salida de un comando con la entrada de otro

Descripción

Al terminar esta lección vas a poder construir tuberías de varios comandos —dos, tres, cuatro eslabones— para responder preguntas sobre archivos que nunca podrías revisar a mano: cuántas líneas de error hay en un log de decenas de miles de renglones, cuántos archivos de cierto tipo existen en todo el sistema, cuáles de los miles de resultados de una búsqueda valen la pena mirar de cerca. Vas a construirlas con un método concreto —por partes, verificando cada eslabón— y vas a saber qué hacer cuando el siguiente comando de la cadena se niega a leer lo que le mandas.

Esto es exactamente lo que hace un desarrollador o un administrador de sistemas cuando un servidor falla a las tres de la mañana: nadie abre un archivo de log de dos millones de líneas en un editor de texto a buscar a ojo. Se conectan tres o cuatro comandos pequeños, cada uno haciendo una sola cosa bien, hasta que la pregunta queda reducida a una línea de respuesta. Eso es literalmente la filosofía Unix de la que hablaste en la segunda lección de este módulo, puesta en práctica con un solo símbolo.

Conexión con el módulo: en la lección anterior conectaste la salida de un comando con un archivo>, >>, tee—. Ahora vas a conectar la salida de un comando directamente con la entrada de otro comando, sin que nada toque el disco en el medio. Es la misma idea de "mover un flujo a otro destino" que ya conoces; el destino, esta vez, es un proceso vivo en vez de un archivo quieto.


El operador |: una cadena de montaje, no un depósito intermedio

Piensa en una cadena de montaje de fábrica. Una máquina corta la tela, la siguiente la cose, la siguiente le pone botones. Ninguna pieza a medio hacer se guarda en un depósito entre máquina y máquina —pasa directo de una banda transportadora a la siguiente, en el momento en que sale de una y antes de que la otra la necesite—. Si quisieras guardar cada pieza a medio terminar en una caja, etiquetarla, y que el siguiente obrero fuera a buscarla a la caja, el proceso sería el mismo al final, pero mucho más lento y con un paso extra que nadie pidió.

El operador | (la barra vertical, arriba de la tecla Enter en la mayoría de los teclados) hace exactamente eso con comandos: toma la salida estándar (descriptor 1) del comando de la izquierda y la conecta directamente a la entrada estándar (descriptor 0) del comando de la derecha. Los dos comandos arrancan casi al mismo tiempo, como procesos independientes, y el sistema operativo va empujando el texto de uno al otro a medida que se produce —sin escribir nada en disco, sin un archivo temporal que después tengas que borrar—.

comando1 | comando2

Compáralo con la alternativa sin tubería, usando lo que ya sabes de la lección anterior:

comando1 > temp.txt
comando2 < temp.txt
rm temp.txt

Tres líneas, un archivo intermedio que existe solo para ser borrado un segundo después, y un nombre (temp.txt) que tienes que inventar y recordar limpiar. La tubería reemplaza las tres líneas por una sola, y no deja ningún archivo suelto atrás.

Ejemplo trabajado

Tienes el mismo access.log que usaste en la lección de grep del módulo anterior:

INFO  2026-07-18 09:12:03 User 42 logged in
ERROR 2026-07-18 09:14:51 Payment gateway timeout for order 1188
WARN  2026-07-18 09:15:02 Retry scheduled for order 1188
ERROR 2026-07-18 09:15:10 Payment gateway timeout for order 1188
INFO  2026-07-18 09:20:44 User 17 logged in
ERROR 2026-07-18 09:22:19 Database connection refused

La pregunta de hoy es más específica que "muéstrame los errores": quieres saber cuántos de esos errores son, puntualmente, del gateway de pagos. Construyes la tubería de a un eslabón, verificando cada uno antes de agregar el siguiente.

Paso 1 — el primer comando, solo, verificado.

grep ERROR access.log

Qué esperar:

ERROR 2026-07-18 09:14:51 Payment gateway timeout for order 1188
ERROR 2026-07-18 09:15:10 Payment gateway timeout for order 1188
ERROR 2026-07-18 09:22:19 Database connection refused

Tres líneas. Revisas que sean las que esperabas —sí, son los tres errores— antes de seguir.

Paso 2 — agregas el segundo eslabón y vuelves a verificar.

grep ERROR access.log | grep Payment

Qué esperar:

ERROR 2026-07-18 09:14:51 Payment gateway timeout for order 1188
ERROR 2026-07-18 09:15:10 Payment gateway timeout for order 1188

Las tres líneas de grep ERROR entraron, una por una, a la entrada estándar del segundo grep, que se quedó solo con las que además contienen "Payment". Bajaste de tres a dos. Exactamente lo que buscabas: la línea de "Database connection refused" no tenía nada que ver con pagos, y quedó afuera.

Paso 3 — agregas el último eslabón, el que responde la pregunta con un solo número.

grep ERROR access.log | grep Payment | wc -l

Qué esperar:

2

Tres comandos, cada uno haciendo una sola cosa: filtrar por "ERROR", filtrar por "Payment", contar líneas. Ninguno sabe nada del otro más allá de "recibo texto, lo proceso, entrego texto". Esa ignorancia mutua es la razón por la que puedes combinarlos en cualquier orden sensato y con cualquier cantidad de eslabones.


Constrúyela por partes: verifica cada eslabón antes de seguir

Lo que acabas de hacer arriba no fue casualidad de este ejemplo puntual — es el método que quieres usar siempre que la tubería tenga más de un eslabón, sobre todo si alguno de ellos puede borrar, mover o sobrescribir algo:

  1. Ejecuta el primer comando solo, sin ningún | todavía, y confirma que su salida es la que esperabas.
  2. Agrega un solo eslabón más y vuelve a mirar el resultado. Si algo salió distinto de lo que pensabas, el problema está en ese último eslabón que acabas de agregar —no tienes que sospechar de toda la cadena.
  3. Repite hasta llegar al comando final.
  4. Recién ahí, si el resultado final va a alimentar algo destructivo o irreversible (un rm, un mv, un script que modifica producción), corre la tubería completa una vez más para confirmarla antes de dejarla suelta.

Escribir la tubería de una sola vez, de punta a punta, y recién ahí correrla, es la forma más común de perder media hora buscando en cuál de los cuatro comandos está el error. Escribir un eslabón, correr, mirar, agregar el siguiente, es más lento la primera vez y muchísimo más rápido en total.


Combinaciones inmediatas con lo que ya sabes

No necesitas herramientas nuevas para que una tubería te sea útil desde hoy mismo. Con lo que ya conoces de los módulos anteriores, estas tres combinaciones resuelven preguntas reales todo el tiempo:

Contar cuántas entradas produjo un comando, sin contarlas a ojo:

ls -l /etc | wc -l

Qué esperar: un número —la cantidad de líneas que imprimió ls -l /etc— en vez del listado completo desplazándose por la pantalla.

Revisar con calma los resultados de una búsqueda recursiva, cuando son demasiados para que quepan en una sola pantalla:

grep -r "TODO" src/ | less

Qué esperar: en vez de que cientos de coincidencias inunden la terminal de golpe, less te las muestra de a una pantalla, y puedes avanzar con la barra espaciadora o buscar dentro de ese resultado con /.

Quedarte solo con los primeros resultados de una búsqueda que puede tardar y producir miles de líneas:

find / -name "*.py" 2> /dev/null | head

Qué esperar: las primeras diez líneas (el valor por defecto de head) de la lista de archivos .py de todo el sistema, en vez de esperar a que find termine de recorrer cada directorio para recién entonces ver algo en pantalla.

En los tres casos pasó lo mismo: un comando que ya conocías produjo demasiada salida para mirarla directamente, y una tubería de un solo eslabón la convirtió en algo manejable.


El error clásico: comandos que no leen la entrada estándar

No todos los comandos leen su entrada estándar. Algunos —rm, cp, mv entre los más comunes— solo miran sus argumentos de línea de comandos (lo que técnicamente se llama argv): la lista de nombres que escribiste después del comando, separados por espacios. Para esos comandos, lo que les llega por una tubería es invisible; ni siquiera lo miran.

Imagina un directorio reports/ con archivos temporales que un script dejó atrás:

ls reports/

Qué esperar:

monthly-summary.tmp
weekly-summary.tmp
sales-summary.tmp

Intentas borrarlos encadenando find con rm, como si rm fuera a "recibir" los nombres por la tubería:

find reports/ -name "*.tmp" | rm

Qué esperar: un mensaje de uso o de "falta un operando", y ningún archivo borrado. El texto exacto varía según el sistema —algo como rm: missing operand en Linux (GNU coreutils), o un usage: rm [-f | -i] ... file ... en macOS (BSD rm)— pero el resultado es idéntico en los dos: rm se queja de que le falta un argumento obligatorio, porque, en efecto, no se le pasó ninguno. El texto que find mandó por la tubería llegó a la entrada estándar de rm, pero rm jamás mira su entrada estándar para decidir qué borrar: solo mira argv, y argv vino vacío.

La solución es xargs. Lee lo que le llega por la entrada estándar, línea por línea, y lo convierte en argumentos de línea de comandos para el comando que le sigue:

find reports/ -name "*.tmp" | xargs rm

Qué esperar: nada en pantalla —rm recibió monthly-summary.tmp weekly-summary.tmp sales-summary.tmp como argumentos reales, uno por uno, y los tres archivos desaparecieron sin queja alguna—.

El nombre raro que rompe todo: espacios en un nombre de archivo

Ahora un compañero deja un archivo con un nombre que no sigue la convención del equipo:

find reports/ -name "*.tmp"

Qué esperar:

reports/old report.tmp

Repites la misma tubería que acabas de aprender:

find reports/ -name "*.tmp" | xargs rm

Qué esperar:

rm: reports/old: No such file or directory
rm: report.tmp: No such file or directory

Nada se borró, y los dos "archivos" que rm intentó borrar ni siquiera existen. Por defecto, xargs separa los argumentos por espacios en blanco —igual que separarías palabras al escribir en la línea de comandos—. La única línea que le llegó, reports/old report.tmp, la partió en dos: reports/old y report.tmp. Ninguno de los dos es el archivo real.

La solución exacta: find puede terminar cada resultado con un byte nulo (\0) en vez de un salto de línea, con -print0. Y xargs -0 le dice a xargs que use ese mismo byte nulo como separador, en vez de espacios o saltos de línea:

find reports/ -name "*.tmp" -print0 | xargs -0 rm

Qué esperar: sin salida en pantalla —y reports/old report.tmp, el archivo completo, con el espacio adentro, desapareció—.

¿Por qué el byte nulo y no cualquier otro separador? Porque es el único byte que un nombre de archivo no puede contener en un sistema Unix (la barra / tampoco puede aparecer dentro de un nombre, pero esa ya la usas para separar directorios). Un espacio, un tab, e incluso un salto de línea sí pueden ser parte legítima de un nombre de archivo real —raro, pero legal—. El byte nulo, no. Por eso -print0 junto con xargs -0 es la única combinación que garantiza que ningún nombre, por más raro que sea, se corte donde no debe.


Cómo se comportan los errores dentro de una tubería

El operador | conecta exactamente un flujo: la salida estándar (descriptor 1) de la izquierda con la entrada estándar (descriptor 0) de la derecha. Nunca toca la salida de error (descriptor 2) de ningún comando de la cadena. Esto tiene una consecuencia que sorprende la primera vez que la ves:

grep ERROR missing-file.log | wc -l

Qué esperar:

grep: missing-file.log: No such file or directory
0

Dos cosas pasaron, y ninguna tiene que ver con la otra. grep no pudo abrir un archivo que no existe, y mandó ese error directo a la pantalla por su descriptor 2 —la tubería nunca lo tocó, porque la tubería solo mueve el descriptor 1—. Al mismo tiempo, como grep no produjo ninguna línea por su descriptor 1 (no tenía nada que buscar), wc -l recibió una entrada completamente vacía, y contó, correctamente, cero líneas en ella. El 0 que ves no significa "no hubo errores en el log" —significa "el log, que ni siquiera existe, no le mandó ninguna línea a wc"—. Son dos resultados independientes, mezclados en la misma pantalla por casualidad de que ambos comandos escriben ahí por defecto.

Cada comando de una tubería corre como un proceso separado, casi en simultáneo, y cada uno termina con su propio resultado interno de éxito o fracaso —de eso, y de cómo usarlo para encadenar decisiones, se ocupa la última lección de este módulo—. Por ahora, quédate con la idea central: una tubería nunca oculta ni redirige los errores por sí sola. Si quieres que también viajen por la tubería, necesitas pedirlo explícitamente con 2>&1, como viste en la lección anterior.


Errores comunes

1. Pensar que | también combina los errores, igual que 2>&1 (conceptual). Qué pasa: corres comando1 | comando2 esperando que, si comando1 falla, ese error quede filtrado o "absorbido" dentro de la tubería, y en cambio lo sigues viendo en pantalla, mezclado con lo que sea que imprima comando2. Por qué: | conecta un único flujo —descriptor 1 con descriptor 0—, nunca el descriptor 2. Combinar los errores requiere pedirlo aparte, con 2>&1 antes del |. Cómo detectarlo: ves mensajes de error en la terminal que "no deberían estar ahí" porque asumías que ya estaban filtrados o redirigidos. Cómo corregirlo: si de verdad quieres que los errores entren a la tubería, escribe comando1 2>&1 | comando2.

2. Canalizar hacia un comando que no lee la entrada estándar. Qué pasa: escribes find . -name "*.bak" | rm (o el mismo patrón con cp o mv) esperando que el comando final "reciba" los nombres por la tubería, y en cambio falla de inmediato con un mensaje de uso (usage: ...) sin tocar ningún archivo. Por qué: rm, cp y mv toman los nombres de archivo como argumentos de línea de comandos, no desde la entrada estándar; lo que les llega por el pipe es invisible para ellos. Cómo detectarlo: el comando termina casi al instante con un mensaje de "uso" o "falta un operando" en vez de quejarse de un archivo puntual. Cómo corregirlo: intercala xargs entre los dos comandos (find . -name "*.bak" | xargs rm), que sí lee la entrada estándar y la convierte en argumentos.

3. Usar xargs sin -0 cuando los nombres de archivo pueden tener espacios o saltos de línea. Qué pasa: find directorio -name "*.tmp" | xargs rm falla con "No such file or directory" para archivos que sí existen, o —peor— borra algo que nunca quisiste tocar porque uno de los fragmentos cortados coincidió por accidente con otro nombre real. Por qué: sin -0, tanto find como xargs usan espacios en blanco y saltos de línea como separadores; si un nombre real contiene alguno de esos caracteres, se parte en el lugar equivocado y deja de ser un solo argumento. Cómo detectarlo: antes de canalizar hacia un comando destructivo, revisa si algún nombre del directorio tiene espacios (ls -l ya te lo muestra, aunque sea fácil no prestarle atención). Cómo corregirlo: usa siempre find ... -print0 | xargs -0 ... cuando el comando final borra, mueve o modifica archivos —el byte nulo es el único carácter que ningún nombre de archivo puede contener, así que nunca corta donde no debe.


Ejercicios

1. Tienes un archivo orders.log con miles de líneas que empiezan con INFO, WARN o ERROR. Quieres saber cuántas líneas de error mencionan la palabra timeout. Nunca escribiste esta tubería antes. Describe, paso a paso, cómo la construirías y verificarías —no solo cuál es el comando final.

Ver solución

Paso 1: grep ERROR orders.log solo, sin nada más, para confirmar que las líneas que aparecen son en efecto los errores que esperas ver.

Paso 2: agregar el segundo filtro y volver a mirar: grep ERROR orders.log | grep timeout — confirmas que el subconjunto que queda tiene sentido (menos líneas que en el paso 1, todas mencionando "timeout").

Paso 3: agregar el conteo final: grep ERROR orders.log | grep timeout | wc -l — ahí sí tienes la respuesta a la pregunta original, un solo número.

Por qué funciona: verificar cada eslabón antes de agregar el siguiente significa que, si algo sale mal, sabes que el problema está en el último comando que agregaste —no tienes que sospechar de toda la cadena de una vez.

2. Ejecutas grep ERROR sales.log | wc -l en un directorio donde sales.log no existe. ¿Qué aparece exactamente en la pantalla, y por qué el número que imprime wc -l no significa lo que a primera vista parece significar?

Ver solución

Aparecen dos cosas independientes:

grep: sales.log: No such file or directory
0

El mensaje de error de grep sale directo a la pantalla por su descriptor de error (2) —la tubería nunca lo toca, porque | solo conecta el descriptor 1 con la entrada del siguiente comando—. Como grep no produjo ninguna línea por su descriptor 1 (no había archivo que leer), wc -l recibió una entrada vacía y contó, correctamente, cero líneas en ella.

Por qué funciona (o, en este caso, por qué engaña): el 0 no significa "no hay errores en las ventas" —significa "no le llegó ninguna línea a wc", que es una situación completamente distinta y mucho más grave: el archivo que querías revisar ni siquiera existe.

3. Este comando falla y no borra nada:

find backups/ -name "*.old" | rm

¿Por qué falla exactamente, y cómo lo corriges?

Ver solución

Falla porque rm no lee la entrada estándar en absoluto: solo mira los argumentos de línea de comandos que le pasas directamente. El texto que find manda por la tubería llega a la entrada estándar de rm, pero rm nunca la consulta para decidir qué borrar —por eso termina quejándose de que le falta un argumento, sin tocar ningún archivo.

La corrección es intercalar xargs, que sí lee la entrada estándar y la convierte en argumentos:

find backups/ -name "*.old" | xargs rm

Por qué funciona: xargs es el traductor entre "texto que llega por una tubería" y "argumentos de línea de comandos", que es el único idioma que rm entiende.

4. Un directorio archive/ tiene, entre otros, un archivo llamado final report.old (con un espacio en el nombre). Corres find archive/ -name "*.old" | xargs rm y ves este error:

rm: archive/final: No such file or directory
rm: report.old: No such file or directory

¿Qué salió mal, y cuál es la tubería correcta para borrar ese archivo sin importar qué caracteres raros tenga su nombre?

Ver solución

Salió mal porque, sin ningún indicador especial, tanto find como xargs usan espacios en blanco y saltos de línea como separadores. La única línea que produjo find, archive/final report.old, xargs la partió en dos argumentos por el espacio que contiene: archive/final y report.old —ninguno de los dos es el nombre real del archivo.

La tubería correcta usa el byte nulo como separador, con -print0 en find y -0 en xargs:

find archive/ -name "*.old" -print0 | xargs -0 rm

Por qué funciona: el byte nulo es el único carácter que un nombre de archivo real no puede contener, así que usarlo como separador es la única forma de garantizar que un nombre con espacios, tabs o incluso saltos de línea llegue completo a rm, sin cortarse en un lugar equivocado.


Resumen y siguiente paso

Ya puedes conectar la salida de un comando directamente con la entrada del siguiente usando |, sin pasar por un archivo intermedio. Sabes construir esa tubería por partes —verificando cada eslabón antes de agregar el próximo— en vez de escribirla entera y recién ahí descubrir dónde falló. Reconoces cuándo el comando final de una cadena no lee la entrada estándar y necesita xargs para recibir los nombres como argumentos, y sabes por qué -print0 junto con xargs -0 es la única combinación segura cuando los nombres de archivo pueden tener espacios o caracteres raros. Y entendiste que una tubería nunca oculta los errores por sí sola: cada comando de la cadena sigue mandando su descriptor de error directo a la pantalla, a menos que se lo pidas explícitamente.

Todo lo que conectaste en esta lección fueron piezas que ya conocías de módulos anteriores: ls, find, grep, wc, head, less. La tubería en sí misma es solo la plomería —conecta, pero no transforma nada por su cuenta—. Lo que multiplica su poder real son las herramientas diseñadas específicamente para vivir dentro de una tubería: tomar texto crudo por un lado y entregar texto ordenado, contado o traducido por el otro. Eso es exactamente lo que sigue: sort, uniq, cut, tr y sed, el kit de texto que convierte una tubería de dos comandos en un instrumento real de análisis.

Antes de avanzar deberías poder:

  • explicar con tus propias palabras qué conecta exactamente el operador |, y qué es lo que no conecta (el descriptor de error de ningún comando de la cadena);
  • construir una tubería de al menos tres comandos, agregando y verificando un eslabón a la vez, sin escribirla entera de un tirón;
  • reconocer cuándo un comando no lee la entrada estándar y necesita xargs, y usar -print0 junto con xargs -0 cuando los nombres de archivo pueden tener espacios o caracteres poco comunes.

Recursos