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
- Los tres flujos estándar
- Redirigir la salida:
>y>> - El riesgo de truncado y
noclobber - Redirigir la entrada:
< - Redirigir los errores:
2>,2>&1y&> /dev/null: el agujero negro del sistematee: ver y guardar a la vez- Tuberías y el concepto de filtro
- Cadenas de filtros que responden preguntas reales
- El código de salida de una tubería
- 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.
- Redirigir la salida:
> y >>
> y >>El operador > envía stdout a un fichero en lugar de a la pantalla:
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.logRegla 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 >>.
- El riesgo de truncado y
noclobber
noclobberEl 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:
Con noclobber activo, > se niega a sobrescribir un fichero que ya existe. Cuando sí 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ónFí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.
- Redirigir la entrada:
<
<El operador < hace que un comando lea de un fichero en lugar del teclado:
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.
- Redirigir los errores:
2>, 2>&1 y &>
2>, 2>&1 y &>Como stderr es el descriptor 2, se redirige anteponiendo ese número al operador:
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»:
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).
/dev/null: el agujero negro del sistema
/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 todoEl 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:
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.
tee: ver y guardar a la vez
tee: ver y guardar a la veztee (por la «T» de fontanería) duplica el flujo: lo escribe en un fichero y lo deja seguir por stdout.
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:
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.
- 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.
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 comogrep -c ERROR /var/log/veloz/app.log: un proceso en lugar de tres.caten cabeza de tubería solo se justifica cuando concatenas varios ficheros.
- 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.txtPaso 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.txtY 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.
- 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 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
> ficheroqueriendo>>. Destruye el contenido al instante. Consideraset -o noclobberen tu~/.bashrc. - Poner
2>&1antes de>. Los errores se quedan en la pantalla. La forma correcta escmd > fich 2>&1, o directamentecmd &> 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. Usasudo 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 > ficherodeja el fichero vacío, porque>lo trunca antes de quesortlo lea. Escribe en un temporal y luego renombra conmv.
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"; fiExplica 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.logLas 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.txtgrep ',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:
$?recoge el código dewc, no el degrep.wc -lsiempre 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.- La redirección
> /dev/null 2>&1no aporta nada útil aquí y de paso descarta el número quewchabí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):
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:
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
- ¿Qué es Bash?
- Configurando tu Entorno
- Navegación Básica en la Línea de Comandos
- Entendiendo el Shell
- Encontrar Ayuda: man, help y --help
Módulo 2: Comandos Básicos de Bash
- Operaciones con Archivos y Directorios
- Comandos de Procesamiento de Texto
- Permisos y Propiedad de Archivos
- Redirección y Tuberías
- Comodines y Expansión de Rutas
- Historial y Atajos de Teclado
Módulo 3: Fundamentos de Scripting
- Creando y Ejecutando un Script
- Variables y Constantes
- Operadores Básicos
- Sentencias Condicionales
- Argumentos y Entrada del Usuario
- Comillas, Expansión y Sustitución
Módulo 4: Scripting Intermedio
- Bucles en Bash
- Funciones en Bash
- Arrays y Arrays Asociativos
- Manipulación de Cadenas
- La Sentencia case y los Menús Interactivos
- Aritmética y Cálculos Numéricos
Módulo 5: Técnicas Avanzadas de Scripting
- Operaciones Avanzadas con Archivos
- Gestión de Procesos
- Manejo de Errores y Depuración
- Expresiones Regulares
- Entrada/Salida Avanzada: Descriptores y Here-Documents
- Scripts Modulares y Librerías Reutilizables
Módulo 6: Trabajando con Herramientas Externas
Módulo 7: Automatización y Programación
- Trabajos Cron
- Automatizando Tareas
- Scripts de Respaldo y Restauración
- Monitoreo y Registro
- Servicios y Temporizadores con systemd
- Automatización Remota con SSH
Módulo 8: Mejores Prácticas y Optimización
- Escribiendo Código Legible
- Optimizando Scripts en Bash
- Consideraciones de Seguridad
- Control de Versiones con Git
- Análisis Estático con ShellCheck y shfmt
- Pruebas Automatizadas con Bats
- Portabilidad: POSIX sh frente a Bashismos
