En la lección 02-02 ya usaste | de forma intuitiva para encadenar cut, sort y uniq. Ahora vas a entender qué ocurre por debajo y a dominar la otra mitad del mecanismo: la redirección. Esta es la lección que convierte una colección de comandos sueltos en un lenguaje de composición. Y es también la que necesitas para que veloz-ops deje de imprimir en pantalla y empiece a generar ficheros de informe y registros de ejecución, que es lo que se espera de una herramienta de operaciones seria.

Contenido

  1. Los tres flujos estándar
  2. Redirigir la salida: > y >>
  3. El riesgo de truncado y noclobber
  4. Redirigir la entrada: <
  5. Redirigir los errores: 2>, 2>&1 y &>
  6. /dev/null: el agujero negro del sistema
  7. tee: ver y guardar a la vez
  8. Tuberías y el concepto de filtro
  9. Cadenas de filtros que responden preguntas reales
  10. El código de salida de una tubería

  1. Los tres flujos estándar

Todo proceso en Linux nace con tres canales de comunicación abiertos, identificados por un número llamado descriptor de fichero:

Nombre Descriptor Dirección Destino por defecto
stdin (entrada estándar) 0 Entra El teclado
stdout (salida estándar) 1 Sale La pantalla
stderr (salida de error) 2 Sale La pantalla
flowchart LR
    T[Teclado o fichero] -->|stdin 0| C[comando]
    C -->|stdout 1| S[Pantalla, fichero o siguiente comando]
    C -->|stderr 2| E[Pantalla o fichero de errores]

La pregunta obvia es por qué hay dos salidas si ambas acaban en la pantalla. La respuesta es la clave de todo el diseño: para poder separarlas cuando hace falta. Un comando escribe sus resultados en stdout y sus quejas en stderr, de modo que puedes guardar los resultados en un fichero mientras los errores siguen apareciendo en tu terminal, o justo al revés. Compruébalo:

Si ejecutas ls /srv/veloz/datos /srv/veloz/inexistente, verás mezclados el listado del primer directorio y el mensaje No existe el fichero o el directorio del segundo. Parecen un solo bloque, pero son flujos distintos: en cuanto redirijas uno, se separarán.

  1. Redirigir la salida: > y >>

El operador > envía stdout a un fichero en lugar de a la pantalla:

cut -d, -f3 /srv/veloz/datos/envios.csv | sort | uniq -c > ~/veloz-ops/logs/ciudades.txt

No verás nada por pantalla: la salida está en el fichero. > crea el fichero si no existe y lo vacía por completo si existe. Ese vaciado es total e instantáneo, y ocurre antes de que el comando empiece a ejecutarse.

El operador >> añade al final sin borrar lo anterior:

date >> ~/veloz-ops/logs/ejecuciones.log
echo "informe diario completado" >> ~/veloz-ops/logs/ejecuciones.log
lun ago  3 11:52:03 CEST 2026
informe diario completado

Regla práctica: > para resultados que se regeneran, >> para registros que se acumulan. Un informe diario que se rehace cada mañana usa >; el log de ejecuciones del toolkit usa >>.

  1. El riesgo de truncado y noclobber

El peligro de > es que no avisa. Escribir por error > envios.csv en lugar de >> envios.csv destruye el fichero de datos en el acto, sin confirmación y sin recuperación posible. Es el equivalente silencioso del rm de la lección 02-01.

Bash ofrece una red de seguridad, la opción noclobber:

set -o noclobber
echo "prueba" > ~/veloz-ops/logs/ciudades.txt
bash: /home/joan/veloz-ops/logs/ciudades.txt: no se puede sobreescribir el fichero existente

Con noclobber activo, > se niega a sobrescribir un fichero que ya existe. Cuando quieras hacerlo, se usa el operador >|, que fuerza el truncado:

echo "prueba" >| ~/veloz-ops/logs/ciudades.txt   # sobrescribe pese a noclobber
set +o noclobber                                  # desactivar la opción

Fíjate en la asimetría de set: -o activa una opción y +o la desactiva, al revés de lo que sugiere la intuición. Activar noclobber en tu ~/.bashrc es una buena idea para sesiones interactivas; en scripts se prefiere no depender de él y escribir las redirecciones con cuidado.

  1. Redirigir la entrada: <

El operador < hace que un comando lea de un fichero en lugar del teclado:

wc -l < /srv/veloz/datos/envios.csv    # → 1247, sin el nombre del fichero

Compáralo con wc -l /srv/veloz/datos/envios.csv, que imprime también el nombre del fichero. La diferencia es conceptual: en el primer caso wc no sabe que hay un fichero, solo recibe un flujo de datos por stdin; en el segundo, abre el fichero él mismo. Esa distinción importará cuando escribas bucles que leen ficheros línea a línea en el Módulo 4.

Hay comandos que solo aceptan entrada estándar, como tr, que no admite un fichero como argumento: tr 'a-z' 'A-Z' < envios.csv es la única forma de alimentarlo desde un fichero.

  1. Redirigir los errores: 2>, 2>&1 y &>

Como stderr es el descriptor 2, se redirige anteponiendo ese número al operador:

ls /srv/veloz/datos /srv/veloz/inexistente > salida.txt 2> errores.txt

Ahora salida.txt contiene solo el listado correcto y errores.txt solo el mensaje de error. 2>> añade en lugar de truncar, exactamente igual que >>.

Para combinar ambos flujos en el mismo destino se usa 2>&1, que se lee «envía el descriptor 2 allí donde apunta ahora mismo el descriptor 1»:

./informe-diario.sh > ~/veloz-ops/logs/informe.log 2>&1

El orden importa, y es la trampa clásica. Bash procesa las redirecciones de izquierda a derecha:

Escritura Qué ocurre
cmd > fich 2>&1 1 va al fichero; después 2 se apunta a donde está 1 → ambos al fichero
cmd 2>&1 > fich 2 se apunta a donde está 1, que todavía es la pantalla; después 1 va al fichero → stdout al fichero, stderr a la pantalla

La segunda forma es un error real y frecuente: parece que guarda todo y en realidad los errores se pierden en la terminal. Recuerda que 2>&1 copia el destino actual de 1, no crea un vínculo permanente.

Bash ofrece además una forma abreviada propia, más legible y sin riesgo de orden:

./informe-diario.sh &>  ~/veloz-ops/logs/informe.log   # todo al fichero (trunca)
./informe-diario.sh &>> ~/veloz-ops/logs/informe.log   # todo al fichero (añade)

&> no es POSIX: funciona en Bash pero no en sh puro. Para scripts portables se usa > fich 2>&1 (portabilidad, 08-07).

  1. /dev/null: el agujero negro del sistema

/dev/null es un fichero especial que descarta todo lo que se escribe en él y devuelve vacío al leerlo. Sirve para silenciar salidas que no interesan:

grep -q ERROR /var/log/veloz/app.log 2>/dev/null   # silenciar solo los errores
comando >/dev/null 2>&1                            # silenciar absolutamente todo

El segundo patrón es uno de los más frecuentes en scripting. Se usa cuando solo te importa el código de salida del comando, no su salida, típicamente en una comprobación:

if ping -c1 srv-veloz-01 >/dev/null 2>&1; then echo "servidor accesible"; fi

Aquí no queremos ver las estadísticas de ping, solo saber si respondió. Ahora bien, silenciar errores es peligroso: 2>/dev/null en un script de producción puede ocultar exactamente el fallo que necesitabas ver. Úsalo solo cuando el error sea esperado y esté contemplado.

  1. tee: ver y guardar a la vez

tee (por la «T» de fontanería) duplica el flujo: lo escribe en un fichero y lo deja seguir por stdout.

grep ERROR /var/log/veloz/app.log | tee ~/veloz-ops/logs/errores-hoy.log | wc -l
37

En una sola línea has guardado los errores completos en un fichero y has visto el recuento en pantalla. Con -a (append) añade en lugar de truncar, y combinado con sudo resuelve un problema clásico:

echo "veloz-api" | sudo tee -a /etc/veloz/servicios.conf

Esto funciona donde sudo echo ... >> /etc/... falla, y entender por qué es importante: la redirección la hace tu shell, no sudo, así que es tu usuario sin privilegios quien intenta abrir el fichero del sistema. Con tee, el proceso privilegiado es el que escribe.

  1. Tuberías y el concepto de filtro

Una tubería | conecta el stdout del comando de la izquierda con el stdin del de la derecha. Los dos procesos se ejecutan a la vez, no uno detrás de otro: los datos fluyen según se producen, sin ficheros intermedios y sin cargar nada entero en memoria.

Un filtro es un programa que lee de stdin, transforma y escribe en stdout. grep, cut, sort, uniq, tr, wc, head, tail y column son todos filtros, y por eso encajan entre sí en cualquier orden.

cat /var/log/veloz/app.log | grep ERROR | wc -l

Dos advertencias:

  • La tubería solo transporta stdout. Los errores del primer comando van a la pantalla, no al segundo. Si quieres pasarlos también, cmd 2>&1 | filtro (Bash tiene el atajo |&).
  • Evita el «uso inútil de cat». El ejemplo anterior se escribe mejor como grep -c ERROR /var/log/veloz/app.log: un proceso en lugar de tres. cat en cabeza de tubería solo se justifica cuando concatenas varios ficheros.

  1. Cadenas de filtros que responden preguntas reales

Aquí es donde todo el módulo converge. Las 10 IPs que más peticiones han hecho hoy a veloz-api:

cut -d' ' -f1 /var/log/veloz/acceso.log | sort | uniq -c | sort -rn | head -10 \
  | tee ~/veloz-ops/logs/top-ips.txt
   4821 10.20.4.15
   2103 10.20.4.77

Paso a paso: cut -d' ' -f1 extrae el primer campo separado por espacios, que en el formato combinado es la IP → sort agrupa las iguales → uniq -c cuenta cada grupo → sort -rn ordena por número descendente → head -10 se queda con el podio → tee lo guarda además en el toolkit.

Ciudades con más incidencias, guardando el resultado como informe:

grep ',incidencia,' /srv/veloz/datos/envios.csv \
  | cut -d, -f3 | sort | uniq -c | sort -rn \
  | column -t > ~/veloz-ops/logs/incidencias-por-ciudad.txt

Y para contar y registrar a la vez las peticiones que devolvieron error 500: grep ' 500 ' /var/log/veloz/acceso.log | tee ~/veloz-ops/logs/errores-500.log | wc -l.

Fíjate en el patrón general que se repite: filtrar → extraer → agrupar → contar → ordenar → presentar → guardar. Casi cualquier pregunta sobre datos tabulares o logs se responde con alguna variante de esa secuencia, y ese es exactamente el esqueleto del analizador de logs que construirás en el proyecto 09-02.

  1. El código de salida de una tubería

En la lección 01-04 viste $?, el código de salida del último comando. En una tubería, $? es el del último comando de la cadena, no el de toda ella:

grep INEXISTENTE /var/log/veloz/app.log | wc -l   # imprime 0
echo $?                                            # imprime 0 también

grep no encontró nada y devolvió 1, pero wc -l se ejecutó correctamente y devolvió 0, así que $? vale 0. La tubería parece haber tenido éxito cuando en realidad el filtro falló. Es una fuente inagotable de bugs silenciosos en scripts.

Bash ofrece dos soluciones: el array PIPESTATUS, que guarda el código de cada comando de la tubería, y la opción set -o pipefail, que hace que la tubería devuelva el código del primer comando que falló. Ambas pertenecen al manejo de errores y las estudiarás a fondo en 05-03; por ahora, quédate con la advertencia.

Un último apunte: cuando necesites pasar la salida de un comando como argumentos de otro (no como entrada estándar), la tubería no sirve y hace falta xargs, que verás en 05-01. Y las formas avanzadas de E/S —here-documents y descriptores propios— llegan en 05-05.

Errores Comunes y Consejos

  • Escribir > fichero queriendo >>. Destruye el contenido al instante. Considera set -o noclobber en tu ~/.bashrc.
  • Poner 2>&1 antes de >. Los errores se quedan en la pantalla. La forma correcta es cmd > fich 2>&1, o directamente cmd &> fich.
  • Creer que la tubería transporta los errores. Solo lleva stdout; usa 2>&1 | si los necesitas.
  • Usar sudo cmd > /etc/fichero. La redirección la hace tu shell sin privilegios y falla. Usa sudo tee.
  • Abusar de 2>/dev/null. Oculta el diagnóstico que ibas a necesitar. Silencia solo errores esperados.
  • Confiar en $? tras una tubería. Solo refleja el último comando (ver 05-03).
  • Leer y escribir el mismo fichero en un comando. sort fichero > fichero deja el fichero vacío, porque > lo trunca antes de que sort lo lea. Escribe en un temporal y luego renombra con mv.

Ejercicios

Ejercicio 1 — Separar resultados y errores. Ejecuta un ls sobre /srv/veloz/datos y sobre un directorio inexistente, de forma que el listado correcto quede en ~/veloz-ops/logs/listado.txt y el mensaje de error en ~/veloz-ops/logs/listado.err. Después escribe la variante que lo guarde todo junto en un solo fichero, de las dos formas posibles.

Ejercicio 2 — Informe de repartidores con incidencias. Sobre envios.csv, genera un fichero ~/veloz-ops/logs/incidencias-repartidor.txt con el número de incidencias de cada repartidor, ordenado de más a menos y presentado en tabla alineada. Debes ver el resultado en pantalla al mismo tiempo que se guarda.

Ejercicio 3 — Depurar un comando que falla en silencio. Un compañero tiene esta línea en un script y se queja de que «nunca detecta nada»:

grep TIMEOUT /var/log/veloz/app.log | wc -l > /dev/null 2>&1
if [ $? -eq 0 ]; then echo "hay timeouts"; fi

Explica los dos fallos y reescríbela correctamente.

Soluciones

Solución al Ejercicio 1

# Separados
ls /srv/veloz/datos /srv/veloz/inexistente \
   > ~/veloz-ops/logs/listado.txt 2> ~/veloz-ops/logs/listado.err

# Todo junto, forma POSIX (portable)
ls /srv/veloz/datos /srv/veloz/inexistente > ~/veloz-ops/logs/todo.log 2>&1

# Todo junto, forma abreviada de Bash
ls /srv/veloz/datos /srv/veloz/inexistente &> ~/veloz-ops/logs/todo.log

Las dos últimas son equivalentes en Bash. La primera es preferible en scripts que puedan ejecutarse con sh; la segunda es más legible y elimina el riesgo de invertir el orden. Lo que no debes escribir es ls ... 2>&1 > ~/veloz-ops/logs/todo.log, que dejaría los errores en pantalla.

Solución al Ejercicio 2

grep ',incidencia,' /srv/veloz/datos/envios.csv \
  | cut -d, -f4 | sort | uniq -c | sort -rn | column -t \
  | tee ~/veloz-ops/logs/incidencias-repartidor.txt
52  mgarcia
41  alopez
25  jruiz

grep ',incidencia,' filtra por el campo de estado con las comas delante y detrás, lo que evita falsos positivos si esa palabra apareciera en otro campo; cut -d, -f4 extrae el repartidor; sort | uniq -c agrupa y cuenta; sort -rn ordena descendentemente; column -t alinea; y tee al final —en lugar de >— es lo que permite ver el resultado y guardarlo simultáneamente.

Solución al Ejercicio 3

Los dos fallos:

  1. $? recoge el código de wc, no el de grep. wc -l siempre termina con éxito, así que la condición se cumple siempre, haya timeouts o no. El script diría «hay timeouts» incluso con el log vacío.
  2. La redirección > /dev/null 2>&1 no aporta nada útil aquí y de paso descarta el número que wc había calculado, con lo que la información se pierde por completo.

Versión correcta, usando el código de salida de grep (0 si encontró algo, 1 si no):

if grep -q TIMEOUT /var/log/veloz/app.log; then
    echo "hay timeouts"
fi

grep -q (quiet) no imprime nada y devuelve solo el código de salida, así que sustituye a la vez al wc y a la redirección a /dev/null. Si además necesitas el recuento:

n=$(grep -c TIMEOUT /var/log/veloz/app.log)
if [ "$n" -gt 0 ]; then
    echo "hay $n timeouts"
fi

La sintaxis $(...) es sustitución de comandos y las condicionales con if se formalizan en el Módulo 3; aquí lo importante es el diagnóstico: evalúa el código de salida del comando que de verdad decide, nunca el del último eslabón de una tubería.

Conclusión

Has entendido el mecanismo que hace de Bash un lenguaje de composición. Sabes que todo proceso tiene tres flujos —stdin, stdout y stderr—, y por qué separarlos es útil; rediriges salidas con > y >> conociendo el riesgo del truncado y la protección de noclobber; rediriges entrada con <; controlas los errores con 2>, 2>&1 y &>, sabiendo que el orden importa; silencias ruido con /dev/null con la debida prudencia; duplicas flujos con tee, incluso para escribir en ficheros del sistema con sudo; y encadenas filtros con | para responder preguntas que ningún comando resuelve solo. También sabes que $? tras una tubería solo refleja el último eslabón, un detalle que resolverás definitivamente en 05-03.

Con esto, los comandos del Módulo 2 dejan de ser piezas sueltas: son componentes de líneas de montaje que ya producen informes reales en ~/veloz-ops/logs.

En la lección 02-05 cerramos el círculo de la manipulación de ficheros con los comodines. Descubrirás que *.csv no lo interpreta ls ni rm, sino el propio shell antes de ejecutarlos, y que esa diferencia explica desde el error rm * más famoso de la historia hasta por qué un bucle falla cuando un patrón no casa con nada.

Curso de Programación en Bash

Módulo 1: Introducción a Bash

Módulo 2: Comandos Básicos de Bash

Módulo 3: Fundamentos de Scripting

Módulo 4: Scripting Intermedio

Módulo 5: Técnicas Avanzadas de Scripting

Módulo 6: Trabajando con Herramientas Externas

Módulo 7: Automatización y Programación

Módulo 8: Mejores Prácticas y Optimización

Módulo 9: Proyectos del Mundo Real

© Copyright 2026. Todos los derechos reservados