Curso de Shell Scripting

Cuando un script crece y se usa en producción, no basta con que funcione: necesita ser predecible, fácil de depurar y resistente a fallos.

En este módulo veremos:

  1. El shebang correcto.
  2. Opciones de Bash para scripts seguros.
  3. Manejo de errores y limpieza con trap.
  4. Estructura mínima de un proyecto de scripts.

3.1. Shebang recomendado

Al inicio de todo script ejecutable pon:

1#!/usr/bin/env bash

✅ Busca la versión de Bash disponible en el entorno. ✅ Es más portátil que #!/bin/bash.

⚠️ Sin shebang, el script podría ejecutarse con el shell por defecto del sistema (por ejemplo sh), y fallar si usas características específicas de Bash.


3.2. Opciones de Bash para seguridad

Coloca estas opciones justo después del shebang:

1set -Eeuo pipefail

Vamos a entenderlas:

OpciónQué hace
-EHace que los traps de ERR también se activen en funciones o subshells.
-eHace que el script se detenga si un comando falla (retorna ≠ 0).
-uProvoca error si usas una variable no definida (evita errores sutiles).
-o pipefailSi en una tubería un comando falla, el fallo se propaga al script.

⚡ Ejemplo sin opciones

1#!/usr/bin/env bash
2
3echo "Inicio"
4comando_inexistente
5echo "Fin"

Salida:

Inicio
./script.sh: línea 3: comando_inexistente: no se encontró el comando
Fin

👉 El script siguió ejecutando a pesar del error.


⚡ Ejemplo con set -Eeuo pipefail

1#!/usr/bin/env bash
2set -Eeuo pipefail
3
4echo "Inicio"
5comando_inexistente
6echo "Fin"

Salida:

Inicio
./script.sh: línea 5: comando_inexistente: no se encontró el comando

👉 El script se detiene al encontrar el error. Esto evita que se sigan ejecutando pasos que dependen de uno que falló.


3.3. Limpieza y manejo de errores con trap

trap permite ejecutar acciones cuando ocurren eventos específicos.

Sintaxis:

1trap 'comando_a_ejecutar' EVENTO

Eventos más usados:

EventoCuándo ocurre
EXITAl finalizar el script (éxito o fallo).
ERRCuando falla un comando (con set -e).
INTCuando el usuario interrumpe con Ctrl+C.
TERMCuando el sistema pide terminar el proceso.

⚡ Ejemplo práctico: limpieza de temporales

1#!/usr/bin/env bash
2set -Eeuo pipefail
3
4TMPFILE=$(mktemp) # crea un archivo temporal
5echo "Archivo temporal creado: $TMPFILE"
6
7# Limpieza automática al salir
8trap 'rm -f "$TMPFILE"' EXIT
9
10echo "Escribiendo datos..."
11echo "Hola mundo" > "$TMPFILE"
12
13# Simulamos un fallo
14false
15
16echo "Esto nunca se ejecuta"

Salida:

Archivo temporal creado: /tmp/tmp.abcd1234
Escribiendo datos...

👉 Aunque el script falla, el archivo temporal se elimina automáticamente gracias al trap.


3.4. Estructura mínima de proyecto

A medida que los scripts crecen, es mejor organizarlos. Ejemplo de estructura:

mi_proyecto/
├── bin/           # Scripts ejecutables
│   └── main.sh
├── lib/           # Funciones reutilizables
│   └── utils.sh
├── tests/         # Pruebas de los scripts
└── README.md

⚡ Ejemplo de main.sh

1#!/usr/bin/env bash
2set -Eeuo pipefail
3trap 'echo "Ocurrió un error o se cerró el script"' EXIT
4
5source "$(dirname "$0")/../lib/utils.sh"
6
7echo "Inicio del script principal"
8saludar "Angel"

⚡ Ejemplo de utils.sh

1#!/usr/bin/env bash
2
3saludar() {
4  local nombre="$1"
5  echo "¡Hola, $nombre! Bienvenido al script."
6}

Ejecución:

1chmod +x bin/main.sh
2./bin/main.sh

Salida:

Inicio del script principal
¡Hola, Angel! Bienvenido al script.
Ocurrió un error o se cerró el script

👉 El mensaje final se ejecuta al salir por el trap.


🏋️‍♂️ Ejercicio práctico

Crea un proyecto llamado backup_logs con esta estructura:

backup_logs/
├── bin/
│   └── backup.sh
└── lib/
    └── funciones.sh

El script backup.sh debe:

  1. Activar set -Eeuo pipefail.
  2. Usar un trap para mostrar “Backup finalizado” al terminar (éxito o error).
  3. Crear un directorio backups/ (si no existe).
  4. Copiar todos los .log de la carpeta actual a backups/ usando cp.
  5. Mostrar cuántos archivos se copiaron.
  6. En caso de fallo, detenerse.

💡 Pistas:

  • Usa mkdir -p backups.
  • Usa cp *.log backups/ pero controla el error si no hay .log.
  • Usa ls backups/*.log | wc -l para contar.
  • Loading...