Dominando el Manejo de Errores en Shell Scripting para Automatizaciones Robustas 🚀

Rossana SuarezRossana Suarez
10 min read

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

  1. Usa set -e para Parar al Primer Error 🚨 Si agregas set -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
    
  2. set -u para Detectar Variables No Definidas ❓ Con set -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
    
  3. 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
    
  4. || 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"
    
  5. 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 en trap, así centralizas toda la lógica de limpieza.

     cleanup() {
         rm -f /tmp/archivo_temp
         echo "🧹 Limpieza realizada."
     }
     trap cleanup EXIT
    
  6. 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"
    
  7. 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"
    
  8. 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}"
    
  9. 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
    
  10. 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

💡
Hadolint es una herramienta valiosa para cualquier desarrollador que trabaje con Docker, ya que ayuda a mejorar la calidad del código, promueve las mejores prácticas y reduce la posibilidad de errores en la construcción de imágenes de Docker.
#!/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

  1. 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.

  2. Instalación de Dependencias:

    • Si no está disponible localmente, el script descarga la imagen hadolint de Docker para ejecutar el análisis en contenedor.
  3. 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 usando hadolint. Si hay problemas, los muestra como "errores"; si está correcto, indica "OK".

    • Filtrado: Permite incluir o excluir archivos específicos de la verificación.

  4. 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:

  1. Muestra un banner de bienvenida con la versión.

  2. Instala las dependencias.

  3. Verifica los Dockerfiles y muestra resultados.

  4. Imprime un reporte final de la verificación.

Para ejecutar el script sigue estos pasos:

  1. 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
    
  2. 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
    
  3. 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 revisar
     FROM 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.

💡
Bonus Track este script lo puedes ejecutar en herramientas de CI/CD

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.

0
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 🔥