- Activar set -euo pipefail e IFS seguro permite que los scripts bash fallen pronto y eviten errores silenciosos al manejar códigos de salida y variables indefinidas.
- El uso de trap para ERR y señales como INT o TERM facilita registrar fallos, hacer limpieza controlada y mantener el sistema en un estado consistente.
- Un sistema de logging centralizado con niveles y timestamps mejora la trazabilidad, el debugging y la integración de scripts en entornos DevOps y CI/CD.
- La combinación de programación defensiva, validación de entradas y buenas prácticas de depuración hace que los scripts bash sean fiables en producción.
Automatizar tareas con Bash marca la diferencia entre un sistema que te exige estar pendiente todo el día y otro que simplemente funciona solo y te avisa cuando algo va mal. En entornos DevOps y de administración de sistemas, un buen scripting no va solo de encadenar comandos: va de evitar fallos silenciosos, de poder depurar rápido y de dejar rastros claros en logs para saber qué ha pasado y cuándo.
Cuando empiezas a trabajar en serio con scripts, descubres que Bash, por defecto, es bastante permisivo: si algo falla, el script suele seguir adelante como si nada, usando variables vacías o resultados incompletos. De ahí vienen historias de terror como backups truncados (para los que conviene sincronizar con rsync en Linux), borrados masivos por rutas mal construidas o despliegues que parecían correctos pero habían fallado a mitad. Por eso tiene tanto sentido aprender a usar bien set -euo pipefail, trap y un sistema de logging decente.
Por qué Bash sigue siendo clave en DevOps
En el día a día de sistemas y DevOps, Bash sigue siendo la herramienta que siempre está ahí: en servidores Linux, contenedores, máquinas de CI/CD, scripts de mantenimiento… Su mayor ventaja es que no dependes de instalar runtimes ni librerías externas; con el shell que ya trae el sistema puedes automatizar desde chequeos de salud hasta despliegues completos.
Ese poder tiene truco: con scripts mal escritos es demasiado fácil que un comando falle y el resto del script continúe como si todo hubiera salido perfecto. Si a eso le sumas tareas críticas (backups, rotación de logs, borrado de ficheros, ejecuciones programadas), el riesgo no es teórico: tarde o temprano algo acabará rompiéndose sin que te enteres.
Por eso es tan importante que, además de automatizar, incluyas desde el principio manejo de errores, depuración y logging estructurado. Y ahí entran en juego tres piezas que se repiten en todos los buenos ejemplos de scripting en Bash moderno: set -euo pipefail, trap y un sistema de logs coherente.
Ejemplo práctico: script de monitorización con manejo estricto de errores
Un patrón muy útil es crear un script de monitorización que recopile información clave del sistema (CPU, memoria, disco, red, procesos, logs recientes…) y que además falle de forma segura, dejando trazas claras en un fichero de log. Ese tipo de script puede usarse a mano, desde cron, o de apoyo en pipelines de CI/CD.
idea general La idea general es tener un script con:
- Modo estricto activado con set -euo pipefail para fallar pronto y no seguir ejecutando sobre estados inconsistentes.
- trap sobre ERR y señales como INT o TERM para registrar errores y hacer limpieza antes de salir.
- Función de logging centralizada que escriba en consola y a fichero con timestamp y nivel (INFO, WARNING, ERROR).
- Chequeos modulares de CPU, memoria, disco, red y logs del sistema.
- Instalación opcional de dependencias como sysstat, lm-sensors o net-tools, adaptada a Debian/Ubuntu o RHEL/CentOS/Fedora.
habitual En un script de este tipo es habitual ver algo como:
- Variables de color para resaltar avisos y errores en la terminal (RED, GREEN, YELLOW, BLUE, NC).
- Rutas como
/var/log/system-monitor.logpara los logs generales y/var/log/system-metrics.logpara métricas periódicas. - Umbrales configurables de alerta: por ejemplo, ALERT_CPU_THRESHOLD=80, ALERT_MEM_THRESHOLD=80, ALERT_DISK_THRESHOLD=85.
- Un intervalo de monitorización MONITOR_INTERVAL para repetir los chequeos cada X segundos si ejecutas el script en bucle.
Con esa base se construye un script robusto que no solo recopila información, sino que también reacciona correctamente ante fallos, deja evidencias claras y puede integrarse en procesos más grandes.
Activar el modo estricto: set -euo pipefail e IFS seguro
La mayoría de problemas “raros” en Bash vienen de que el shell, por defecto, no considera grave que un comando falle o que una variable no exista. Para forzar un comportamiento más seguro se suele activar lo que mucha gente llama “modo Bash estricto”.
Las piezas básicas de ese modo estricto son:
- set -e: hace que el script termine en cuanto un comando devuelve un código de salida distinto de 0, salvo en algunos contextos especiales (if, while, &&, ||…).
- set -u o set -o nounset: provoca error cuando intentas usar una variable que no está definida, en lugar de tratarla como cadena vacía.
- set -o pipefail: hace que en una tubería el código de salida sea el del primer comando que falle, no solo el del último.
- IFS=$’\n\t’: limita el separador de campos interno a salto de línea y tabulador para evitar que los espacios rompan bucles for sobre listas de ficheros o similares.
Al combinar todo queda algo muy habitual en la cabecera de scripts serios:
set -euo pipefail cabecera segura
IFS=$'\n\t'
Con esto evitas situaciones en las que un comando intermedio de una tubería falla, pero el script sigue adelante porque el último comando devuelve 0. También impides que variables mal escritas o no definidas se conviertan silenciosamente en cadenas vacías, lo que suele ser el origen de rutas incorrectas o condiciones lógicas que nunca se cumplen.
Las trampas de set -e: por qué no es magia y puede romperte el script
Aun siendo muy útil, set -e no es una bala de plata. Su comportamiento tiene matices que pueden dar sustos si no entiendes cómo funciona. Por ejemplo, algunas construcciones como:
- Comandos dentro de
if,whileountil. - Expresiones con
&&y||para control de flujo. - Determinadas expansiones aritméticas.
no disparan el “exit inmediato” como podrías esperar. Un caso clásico es el de expansión aritmética con post-incremento:
((contador++))
La expansión aritmética devuelve un valor que Bash interpreta como código de salida. En el caso del post-incremento, cuando la expresión devuelve 0 se considera éxito, pero en otros momentos puede devolver 1, lo que con set -e hace que el script termine ahí mismo. Algo tan inocente como un contador dentro de un bucle puede provocar que tu script salga antes de tiempo.
En cambio, construcciones como ((++i)) o ((i+=1)) se comportan de forma distinta y no disparan el mismo problema. Esto demuestra que con set -e tienes que conocer bien las construcciones que usas, porque hay muchos casos límite.
Por eso, varios autores y administradores con experiencia recomiendan usar set -e sobre todo en fase de pruebas, como una forma agresiva de encontrar puntos débiles, pero en producción prefieren un enfoque más explícito: comprobar códigos de salida a mano y decidir exactamente cuándo y cómo se aborta el script.
trap: capturar errores y señales para limpiar y loguear bien
El comando trap es el otro gran protagonista cuando hablamos de scripting defensivo en Bash. Básicamente, permite indicar qué comandos o funciones quieres ejecutar cuando el shell reciba una señal o un evento especial (como ERR o EXIT).
usos típicos Los usos típicos de trap en scripts robustos son:
- trap ‘funcion_manejo_error’ ERR: ejecutar lógica de manejo de errores justo cuando un comando falla.
- trap ‘funcion_limpieza’ INT TERM: reaccionar a señales como CTRL+C (SIGINT) o peticiones de parada (SIGTERM).
- trap ‘funcion_finalizacion’ EXIT: ejecutar un bloque al salir el script, haya ido bien o mal.
Imagina un script que, al recibir CTRL+C, en lugar de cortarse en seco, llame a una función limpieza que borre temporales, cierre conexiones o deje un mensaje claro en los logs:
trap 'limpieza' TERM INT acción preventiva
function limpieza(){
echo "Ejecutando limpieza, el usuario uso CTRL + C"
# ... lógica de limpieza ...
}
Este patrón es especialmente útil en procesos largos (backups, ETL, despliegues) donde te interesa poder abortar de forma controlada y sin dejar el sistema en estado inconsistente.
Qué puedes y qué no puedes capturar con trap ERR
ejemplo común Muchas guías proponen usar algo como:
trap 'echo "Error en línea $LINENO" >&2' ERR capturar línea
para registrar fallos con su número de línea. Eso está bien, pero conviene entender sus límites. Trap puede:
- Ejecutar código adicional cuando se dispara ERR, por ejemplo escribir en un log con fecha, script y línea.
- Acceder al código de salida del último comando con
$?. - Conocer la línea de script con $LINENO, muy útil para depuración.
Lo que no puede hacer el trap de ERR es “rescatar” la salida estándar de error (stderr) de un comando que ya ha corrido, salvo que tú mismo la hayas redirigido previamente a un fichero o descriptor que luego puedas leer. El error en sí es un flujo de texto, no un valor único.
Algunos patrones consisten en redirigir el stderr de todo el script a un log temporal y, en el trap, añadir una cabecera con fecha, fichero y línea para poder correlacionar ese bloque de errores con el punto del script en el que se produjo el fallo. Es más trabajo, pero cuando algo peta en producción agradeces tener contexto.
Logging en Bash: registrar lo justo, con formato y sin ensuciar
Si automatizas tareas mínimamente críticas, necesitas saber qué ha pasado, cuándo y con qué resultado. Por eso casi todos los ejemplos de scripting serio acaban incluyendo una función de logging centralizada que unifique el formato.
función típica Una función típica puede tener este aspecto:
log_message() { formato unificado
local level="$1"
local message="$2"
local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
echo -e "${timestamp} ${message}" | tee -a "$LOG_FILE"
}
ejemplos Con algo así puedes llamar a:
- log_message «INFO» «Iniciando comprobación de CPU»
- log_message «WARNING» «Uso de disco por encima del 85%»
- log_message «ERROR» «No se pudo conectar a la base de datos»
y tener siempre el mismo formato, con fecha y nivel bien marcados. Usar tee -a además te permite ver el mensaje por pantalla y guardarlo en un fichero de log al mismo tiempo, algo muy cómodo tanto para uso interactivo como para análisis posterior.
Otra buena práctica es separar logs de actividad (qué hace el script) y logs de métricas o resultados (valores de CPU, RAM, disco, etc.) en ficheros diferentes. Por ejemplo:
- /var/log/system-monitor.log para eventos y mensajes.
- /var/log/system-metrics.log para registros periódicos de estado.
facilita integración Eso facilita luego alimentar herramientas externas, parsers o incluso soluciones de observabilidad sin mezclar mensajes humanos con datos de máquina.
Chequeo de estado del sistema: CPU, memoria, disco, red y logs
Un uso muy recurrente de Bash en entornos Linux es implementar un script de “verificación rápida” del sistema que reúna de golpe la información básica para diagnosticar problemas. Este tipo de script suele incluir varios bloques:
- Carga de CPU: usando
mpstatsi está disponible (gracias al paquete sysstat), o recurriendo atopen modo batch para sacar un resumen de uso de CPU. - Top procesos por CPU: con
ps aux --sort=-%cpu | head -n Npara ver qué está consumiendo más recursos. - Espacio en disco: con
df -h, revisando especialmente la partición raíz y cualquier volumen crítico para aplicaciones o bases de datos (ver cómo liberar espacio en Linux). - Memoria RAM y SWAP: a través de
free -h, para ver cuánto se está usando y si se está tirando excesivamente de swap. - Estado de la red: comprobando conectividad externa con un ping a una IP conocida (típicamente 8.8.8.8) y listando interfaces activas con
ip addr show, y usando herramientas como netcat (nc/ncat) para pruebas más avanzadas. - Logs recientes del sistema: sacando las últimas líneas con
journalctl -no equivalentes, para detectar errores de servicios justo antes de que el script se ejecute.
El resultado suele guardarse en un fichero de log o mostrarse formateado en pantalla, sirviendo como fotografía rápida de salud del sistema que puedes revisar tú o integrar en herramientas de monitorización.
Instalación automática de dependencias y chequeo de privilegios
condiciones previas Un detalle importante de muchos scripts de administración es que, antes de hacer nada serio, verifican que se cumplen dos condiciones:
- El script se está ejecutando como root (o vía sudo).
- Las herramientas necesarias están instaladas en función del sistema operativo.
comprobación root La verificación de root suele hacerse con $EUID:
check_root() { falla si no root
if ; then
log_message "ERROR" "Este script necesita privilegios de root para instalar paquetes."
log_message "WARNING" "Por favor, ejecute con sudo."
exit 1
fi
}
De esta forma evitas errores posteriores por permisos insuficientes y envías un mensaje claro al usuario desde el principio.
detectar distro La instalación de dependencias suele apoyarse en /etc/os-release para detectar la familia de distribución:
- En Debian/Ubuntu se usa
apt-getpara instalar paquetes como sysstat, lm-sensors, net-tools. - En CentOS/RHEL/Fedora se tiraría de
dnfoyumcon paquetes equivalentes (lm_sensors, net-tools, etc.).
Alertas, cron y exportación de logs: llevar el script a producción
Una vez que tienes un script de chequeo o automatización medianamente robusto, el siguiente paso lógico es integrarlo en el día a día:
- Alertas por correo, Slack u otros canales: por ejemplo, si el disco supera el 90% de uso, envías un correo con mailx o usas un webhook de Slack. El patrón típico es comprobar el valor con df/awk/sed y, si supera cierto umbral, disparar la notificación.
- Ejecución periódica con cron: añadir una entrada del estilo
0 8 * * * /ruta/a/script.shpara que se ejecute cada día a una hora concreta. - Redirección de salida a ficheros de log: ejecutar
./script.sh > /var/log/system-check.log 2>&1si quieres registrar toda la salida estándar y de error en un único fichero.
Hay que tener claro que, si tu script está pensado para parar ante el primer error gracias a set -euo pipefail, eso implica que el cron o el pipeline de CI/CD que lo llame verán claramente un código de salida distinto de cero cuando haya un fallo, lo que te permite disparar flujos de recuperación, marcar builds como fallidas, etc.
Depuración en Bash: set -x, -v, -n y la traza en fichero
Bash no tiene un depurador al estilo de otros lenguajes, pero sí proporciona varias opciones de depuración muy potentes que, usadas bien, ahorran muchas horas de “¿por qué narices pasa esto?”:
- set -x o set -o xtrace: muestra cada comando que se ejecuta con todos sus parámetros ya expandidos. Ideal para ver qué valores reales están tomando las variables.
- set -v o set -o verbose: imprime las líneas del script según se leen, antes incluso de la expansión. Útil para entender el flujo.
- set -n o set -o noexec: analiza la sintaxis sin llegar a ejecutar el código, perfecto para cazar comillas, llaves o corchetes que faltan.
- set -u: ya comentado, falla cuando se usan variables no definidas, algo muy útil durante la depuración de errores lógicos.
Además, puedes limitar el efecto de estas opciones a un fragmento concreto del script envolviéndolo con set -x / set +x, por ejemplo:
set -x traza temporal
# bloque problemático
read -p "Pass Dir name : " D_OBJECT
read -p "Pass File name : " F_OBJECT
set +x
#!/bin/bash redirigir traza
exec 6> salida_debug.log
BASH_XTRACEFD="6"
set -x
# ... resto del script ...
Así, toda la información de depuración se guarda en salida_debug.log, y tú puedes seguir viendo en la terminal solo la salida “normal” del script.
Ejemplo típico de bug con variables no definidas
Un problema muy común cuando no usas set -u es invocar variables que no existen sin darte cuenta. Imagina un script que pide un nombre de objeto y luego comprueba si es fichero o directorio, pero se equivoca de nombre de variable:
#!/bin/bash error de variable
read -p "Nombre del Objeto : " OBJECT
if ]; then
echo "$OBJECT es un archivo"
elif ]; then
echo "$OBJECT es un directorio"
fi
Como $OBJECT1 no está definida, Bash la expande a cadena vacía, las pruebas -f y -d no dan error, simplemente no se cumplen, y el script sale con código 0. No pasa “nada”, pero lógicamente el resultado no es el esperado.
Si en cambio ejecutas el script con bash -u scriptname o activas set -u, obtendrás inmediatamente un error de “Unbound variable”, lo que te lleva directo al fallo de tipado. Combinado con bash -x scriptname o set -x, verás además en qué línea concreta y qué valores se están usando, lo que hace la depuración mucho más llevadera.
Redactor apasionado del mundo de los bytes y la tecnología en general. Me encanta compartir mis conocimientos a través de la escritura, y eso es lo que haré en este blog, mostrarte todo lo más interesante sobre gadgets, software, hardware, tendencias tecnológicas, y más. Mi objetivo es ayudarte a navegar por el mundo digital de forma sencilla y entretenida.
