Dominando el Manejo de Errores en Shell Scripting para Automatizaciones Robustas 🚀
Bienvenido a un nuevo capítulo de nuestra serie Mastering Automation, One Script at a Time, donde exploramos técnicas avanzadas para crear scripts sólidos en entornos DevOps. Hoy nos adentramos en el manejo de errores en scripting de shell, una habilidad clave para asegurar que nuestras automatizaciones sean confiables y estén preparadas para cualquier situación real 🌎. Desde tareas de respaldo hasta la administración de servicios y el procesamiento de registros, manejar errores de forma efectiva es fundamental para crear scripts resilientes.
🌟 Temas Clave en Manejo de Errores:
1. Códigos de Estado de Salida (Exit Status) ✅
En Linux, cada comando devuelve un código de estado: 0
indica éxito y cualquier otro valor indica un fallo. Verificar estos códigos te permite controlar el flujo de tus scripts y asegurar que continúan solo si las condiciones son las adecuadas.
2. if
para Manejo de Errores ⚙️
Con sentencias if
, puedes verificar el éxito o fallo de cada comando. Esto te da la oportunidad de implementar medidas correctivas o mostrar mensajes de error informativos cuando algo falla. De esta manera, haces que tus scripts sean más seguros y claros.
3. trap
para Limpieza 🧹
¿Alguna vez te has encontrado con archivos temporales sin borrar o recursos abiertos después de una falla? Aquí es donde trap
cobra importancia: te permite capturar señales (por ejemplo, SIGINT
, EXIT
) y realizar tareas de limpieza automáticamente, asegurando que el sistema no quede en un estado inconsistente.
4. Redirección de Errores 🔀
Al automatizar tareas, es útil poder redirigir mensajes de error a un archivo, ya sea para hacer más eficiente la depuración o simplemente para evitar saturar la terminal. Puedes redirigir errores específicos y mantener el orden en tus registros de ejecución.
5. Mensajes de Error Personalizados 📝
Un error genérico como “Error” no da mucha información. Mejorar la experiencia del usuario con mensajes de error detallados facilita la depuración y el mantenimiento de los scripts, guiando a los usuarios sobre qué salió mal y posibles soluciones.
🔧 Ejercicios Prácticos (¡con Código!):
📝 Ejercicio 1: Verificación del Estado de Salida
En este ejemplo, escribimos un script que intenta crear un directorio y verifica si tuvo éxito. Si la creación falla, imprimimos un mensaje de error personalizado.
#!/bin/bash
mkdir /tmp/mi_directorio
if [ $? -ne 0 ]; then
echo "❌ Error: No se pudo crear el directorio /tmp/mi_directorio"
else
echo "✅ Directorio /tmp/mi_directorio creado exitosamente"
fi
Este script verifica el código de salida de mkdir
. Si falla, el if
se activa e imprime un mensaje que explica el error. ¡Simple y efectivo! 😎
📝 Ejercicio 2: if
para Múltiples Comandos
Ahora extendemos el script anterior para incluir la creación de un archivo dentro del directorio y verificamos cada paso de forma independiente.
#!/bin/bash
mkdir /tmp/mi_directorio
if [ $? -ne 0 ]; then
echo "❌ Error: No se pudo crear el directorio /tmp/mi_directorio"
else
touch /tmp/mi_directorio/mi_archivo.txt
if [ $? -ne 0 ]; then
echo "❌ Error: No se pudo crear el archivo /tmp/mi_directorio/mi_archivo.txt"
else
echo "✅ Archivo /tmp/mi_directorio/mi_archivo.txt creado exitosamente"
fi
fi
Aquí, cada comando se verifica antes de pasar al siguiente, lo que hace que el script sea más robusto y fácil de depurar.
📝 Ejercicio 3: Uso de trap
para Limpieza
Con trap
, vamos a crear un archivo temporal y lo eliminaremos automáticamente al finalizar el script, incluso si ocurre un error.
#!/bin/bash
tempfile=$(mktemp) # Crea un archivo temporal
trap "rm -f $tempfile" EXIT # Elimina el archivo temporal al salir
echo "Archivo temporal creado en: $tempfile"
cat $tempfile
# Simulamos un error para demostrar la limpieza automática
exit 1
Aquí, trap
asegura que el archivo temporal se borre cuando el script termina, sin importar si el script finaliza exitosamente o no. ¡Un sistema limpio siempre es una buena práctica! 🧹
📝 Ejercicio 4: Redirección de Errores
Redirigimos los errores de un comando a un archivo específico en lugar de mostrarlos en la terminal.
#!/bin/bash
cat archivo_inexistente.txt 2> error.log
En este caso, el error se redirige a error.log
, permitiéndote revisar los errores de manera organizada sin interrumpir la terminal.
📝 Ejercicio 5: Mensajes de Error Personalizados
Añadir mensajes personalizados da contexto y ayuda a los usuarios a entender el problema rápidamente.
#!/bin/bash
mkdir /tmp/mi_directorio
if [ $? -ne 0 ]; then
echo "❌ Error: No se pudo crear /tmp/mi_directorio. Verifica si tienes los permisos necesarios."
fi
Este mensaje guía al usuario para revisar los permisos, facilitando la resolución del problema.
🚀 Claves Importantes:
Verifica el estado de salida de comandos críticos para asegurarte de que los scripts se comporten como se espera.
Usa
if
para manejar errores y realizar operaciones condicionadas.Emplea
trap
para manejar salidas inesperadas y realizar tareas de limpieza.Redirige la salida de errores a archivos de log para depuración.
Crea mensajes de error claros e informativos para que la solución de problemas sea más sencilla y rápida.
🔥 Tips & Tricks
Usa
set -e
para Parar al Primer Error 🚨 Si agregasset -e
al inicio de tu script, este se detendrá automáticamente si algún comando falla. Esto es útil en scripts largos, ya que evita que el script siga ejecutándose tras un error.# Detener el script si algún comando falla set -e
set -u
para Detectar Variables No Definidas ❓ Conset -u
, el script lanzará un error y se detendrá si intentas usar una variable que no ha sido definida. Esto es genial para evitar errores debido a variables sin inicializar.# Detectar variables no definidas set -u
Usa
set -o pipefail
para Fallos en Pipelines 🚰 Por defecto, un pipeline (comando1 | comando2
) no detecta errores en cada comando individual.set -o pipefail
asegura que, si uno de los comandos falla, el pipeline completo falle.# Detectar errores en pipelines set -o pipefail
||
para Especificar Acciones en Caso de Error ⚙️ Puedes usar||
después de un comando para ejecutar una acción alternativa si ese comando falla. Es muy útil para dar un paso adicional solo si el primer comando no se ejecutó correctamente.mkdir /tmp/mi_directorio || echo "❌ Error al crear el directorio"
Combina
trap
con Funciones para Mejorar la Limpieza 🧽 En lugar de manejar la limpieza de recursos de manera individual, puedes definir una función de limpieza y llamarla entrap
, así centralizas toda la lógica de limpieza.cleanup() { rm -f /tmp/archivo_temp echo "🧹 Limpieza realizada." } trap cleanup EXIT
Crea Funciones para Manejo de Errores Estandarizados 🔧 Si en tu script hay múltiples puntos donde pueden ocurrir errores, considera crear una función
error_exit
para manejar los mensajes de error y el estado de salida de manera uniforme.error_exit() { echo "❌ Error: $1" >&2 exit 1 } mkdir /tmp/mi_directorio || error_exit "No se pudo crear el directorio"
Registra el Estado de Ejecución en un Archivo de Log 📝 En scripts de producción, es útil registrar cada paso y sus posibles errores en un archivo de log para poder revisar el historial de ejecución y realizar una depuración efectiva.
log_file="script.log" echo "Iniciando el script" >> "$log_file" # Ejemplo de redirección de salida de errores al log mkdir /tmp/mi_directorio 2>> "$log_file"
Utiliza Colores en Mensajes de Error y Éxito 🌈 Los colores pueden ayudar a distinguir entre mensajes de error y éxito. Por ejemplo, usar rojo para errores y verde para éxitos mejora la legibilidad en la terminal.
RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' # Sin color echo -e "${GREEN}✅ Operación exitosa${NC}" echo -e "${RED}❌ Error al ejecutar comando${NC}"
Usa
command || true
para Ignorar Errores No Críticos 🔄 A veces hay errores que no son críticos para la ejecución del script. Usar|| true
permite que el script ignore ese error específico sin detener la ejecución.rm /tmp/archivo_inexistente || true # Ignorar si el archivo no existe
Documenta los Posibles Errores y Soluciones 💬 Siempre es buena práctica documentar dentro del script los errores más comunes y sus soluciones, ya sea como comentarios o en un archivo de ayuda asociado al script.
# Error: Permiso denegado al crear el directorio # Solución: Ejecuta el script con privilegios elevados (sudo)
Vamos con el Fuego
Este script en Bash analiza archivos Dockerfile para asegurarse de que cumplen con las mejores prácticas de codificación, usando la herramienta hadolint
, que se ejecuta dentro de un contenedor Docker.
Sobre Hadolint
#!/usr/bin/env bash
# -------------------------------------------------------------------------------- # #
# DOCKERFILE LINTER BY @ROXSROSS # #
# -------------------------------------------------------------------------------- #
# -------------------------------------------------------------------------------- #
# Configuración del shell #
# -------------------------------------------------------------------------------- #
set -Eeuo pipefail
# -------------------------------------------------------------------------------- #
# Banner Function #
# -------------------------------------------------------------------------------- #
function show_banner() {
cat << "EOF"
____ ____
/ __ \____ _ __/ __ \____ __________
/ /_/ / __ \| |/_/ /_/ / __ \/ ___/ ___/
/ _, _/ /_/ /> </ _, _/ /_/ (__ |__ )
/_/ |_|\____/_/|_/_/ |_|\____/____/____/
Created by @RoxsRoss
Version: ${VERSION:-1.0.0}
EOF
}
# -------------------------------------------------------------------------------- #
# Variables Globales #
# -------------------------------------------------------------------------------- #
USE_DOCKER=true
DOCKER_IMAGE='hadolint/hadolint'
DOCKER_IMAGE_SHORT='hadolint'
TOOL_NAME="${DOCKER_IMAGE_SHORT}"
FILE_TYPE_PATTERN='No Magic Text'
FILE_NAME_PATTERN='Dockerfile$'
SEARCH_ROOT='.'
# Arrays para archivos
include_files=()
exclude_files=()
# Comandos Docker
DOCKER_PULL_COMMAND=('docker' 'pull' '--quiet' "${DOCKER_IMAGE}")
DOCKER_RUN_COMMAND=('docker' 'run' '--rm' '-i' "${DOCKER_IMAGE}")
# Contadores
file_count=0
ok_count=0
fail_count=0
skip_count=0
SHOW_ERRORS=${SHOW_ERRORS:-true}
# -------------------------------------------------------------------------------- #
# Funciones de Utilidad #
# -------------------------------------------------------------------------------- #
function run_command() {
local -a command=("$@")
local output
if ! output=$("${command[@]}" 2>&1); then
echo "${output}"
return 1
fi
echo "${output}"
return 0
}
....
Revisa el código completo en mi repositorio
Funcionamiento del Script
Preparación y Configuración:
Configura el entorno con opciones estrictas de manejo de errores.
Define variables clave, incluyendo el nombre de la imagen de
hadolint
y patrones para detectar archivos Dockerfile.
Instalación de Dependencias:
- Si no está disponible localmente, el script descarga la imagen
hadolint
de Docker para ejecutar el análisis en contenedor.
- Si no está disponible localmente, el script descarga la imagen
Verificación de Archivos Dockerfile:
Detección de Archivos: Busca archivos que coincidan con el patrón
Dockerfile
.Ejecuta
hadolint
: Cada archivo encontrado se analiza usandohadolint
. Si hay problemas, los muestra como "errores"; si está correcto, indica "OK".Filtrado: Permite incluir o excluir archivos específicos de la verificación.
Reporte Final:
- Genera un reporte final con el total de archivos verificados, cuántos pasaron la verificación, cuántos fallaron y cuántos fueron omitidos.
Ejecución
El script se ejecuta automáticamente paso a paso:
Muestra un banner de bienvenida con la versión.
Instala las dependencias.
Verifica los Dockerfiles y muestra resultados.
Imprime un reporte final de la verificación.
Para ejecutar el script sigue estos pasos:
Guardar el Script: Guarda el contenido del script en un archivo, por ejemplo, ./dockerfile-lint.sh
git clone -b devops-cicd-hadolint https://github.com/roxsross/roxs-projects-blog.git
Dar Permisos de Ejecución: Asegúrate de que el script tenga permisos de ejecución. Puedes hacerlo con el siguiente comando en la terminal:
chmod +x ./dockerfile-lint.sh
Ejecutar el Script: Ahora puedes ejecutar el script. Asegúrate de estar en el directorio donde guardaste el script y ejecuta:
💡Pero una recomendación recuerda tener algún Dockerfile para revisarFROM alpine:3.14 RUN apk add curl RUN apk add wget RUN apk add bash CMD ["echo", "Hello Roxsross!"]
./dockerfile-lint.sh
Opciones Adicionales
Si deseas modificar el comportamiento del script (por ejemplo, mostrar o no errores), puedes establecer variables de entorno antes de ejecutar el script. Por ejemplo:
Para ocultar los errores:
SHOW_ERRORS=false ./dockerfile-lint.sh
Asegúrate de que Docker esté instalado y funcionando en tu sistema, ya que el script utiliza comandos de Docker para ejecutar el linter.
Github Actions 🔥
name: Hadolint by Roxs
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
hadoroxs:
name: hadolint-roxsross
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Hadolint by @roxsross
env:
SHOW_ERRORS: true
run: |
bash <(curl -s https://raw.githubusercontent.com/roxsross/roxs-projects-blog/refs/heads/devops-cicd-hadolint/dockerfile-lint.sh)
Vamos con el VIDEO
Conclusión:
El manejo de errores es una habilidad esencial en scripting, asegurando que tus automatizaciones fluyan sin problemas. Estos conceptos fortalecerán tus habilidades en DevOps y te prepararán para abordar tareas de automatización con un enfoque más seguro y organizado. ¡Continúa perfeccionando tus scripts y prepárate para los desafíos reales en tu carrera en DevOps! 🚀
Mantente atento a nuestra serie Mastering Automation, One Script at a Time para aprender más técnicas avanzadas de automatización.
Subscribe to my newsletter
Read articles from Rossana Suarez directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Rossana Suarez
Rossana Suarez
Soy Roxs 👩💻| Software Developer | DevOps | DevSecOps | en @295DevOps 🖼 Content Creator. No se puede crecer si no estas dispuesto a saltar a la zona de peligro 🔥