Publicado en

Shell scripting: primeros pasos con Bash

Script Bash con shebang, variables, condicionales y llamada a función sobre fondo oscuro

Hay tareas que haces en la terminal una y otra vez: hacer backups, limpiar archivos temporales, desplegar la aplicación, verificar el estado de los servicios. Ejecutarlas una a una es manual y propenso a errores. Un script Bash las convierte en un solo comando repetible, automatizable y versionable.

El primer script

#!/bin/bash
echo "Hola, mundo"

La primera línea, el shebang, indica al sistema qué intérprete debe usar para ejecutar el archivo. Sin ella, el sistema podría intentar ejecutarlo con sh u otro intérprete con comportamientos distintos. #!/usr/bin/env bash es la variante más portable: busca bash en el PATH en lugar de asumir que está en /bin/bash.

# Dar permisos de ejecución y ejecutar
chmod +x mi-script.sh
./mi-script.sh

# O ejecutar directamente con bash (sin necesitar chmod +x)
bash mi-script.sh

Modo estricto: set -euo pipefail

Estructura de un script Bash con anotaciones: shebang, set -euo pipefail, variables con valores por defecto, funciones y cómo hacerlo ejecutable

Por defecto, Bash continúa ejecutando aunque un comando falle. set -euo pipefail al inicio del script cambia esto:

#!/bin/bash
set -euo pipefail
# -e: termina si cualquier comando devuelve error (código ≠ 0)
# -u: error si se usa una variable no definida
# -o pipefail: el pipe falla si cualquier comando intermedio falla

Es la diferencia entre un script que silencia errores y uno que falla ruidosamente cuando algo va mal. Para scripts de producción, siempre.

Variables y argumentos

#!/bin/bash
set -euo pipefail

# Variables (sin espacios alrededor del =)
NOMBRE="mundo"
FECHA=$(date +%Y-%m-%d)        # $() captura la salida de un comando
NUMERO=$((2 + 3))              # $(()) para aritmética

# Argumentos del script
# $1, $2, ... → argumentos posicionales
# $# → número de argumentos
# $@ → todos los argumentos como lista
# $0 → nombre del script

# Valor por defecto si el argumento no se pasa
ENTORNO="${1:-development}"

echo "Entorno: $ENTORNO, Fecha: $FECHA"

Condicionales y bucles

Control de flujo en Bash: if/elif/else con operadores de test para archivos y cadenas, bucles for e while, y tabla de operadores más usados en doble corchete
# Condicional con doble corchete [[ ]] (más robusto que [ ])
if [[ -f "$ARCHIVO" ]]; then
  echo "El archivo existe"
elif [[ -d "$ARCHIVO" ]]; then
  echo "Es un directorio"
else
  echo "No existe"
fi

# Bucle for sobre una lista de archivos
for log in /var/log/*.log; do
  echo "Procesando: $log"
  gzip "$log"
done

# Bucle sobre un rango numérico
for i in {1..5}; do
  echo "Iteración $i"
done

# Bucle while
while read -r linea; do
  echo "Línea: $linea"
done < archivo.txt

Funciones

#!/bin/bash
set -euo pipefail

log() {
  local nivel="$1"
  local mensaje="$2"
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$nivel] $mensaje"
}

verificar_comando() {
  if ! command -v "$1" &>/dev/null; then
    log "ERROR" "Comando no encontrado: $1"
    exit 1
  fi
}

# Uso
verificar_comando rsync
verificar_comando ssh
log "INFO" "Todos los comandos están disponibles"

Ejemplo práctico: script de backup

#!/bin/bash
set -euo pipefail

ORIGEN="${1:?'Uso: backup.sh '}"
DESTINO="/backups"
FECHA=$(date +%Y%m%d-%H%M%S)
ARCHIVO="backup-${FECHA}.tar.gz"

# Verificar que el origen existe
if [[ ! -d "$ORIGEN" ]]; then
  echo "Error: el directorio '$ORIGEN' no existe" >&2
  exit 1
fi

# Crear el directorio de backups si no existe
mkdir -p "$DESTINO"

# Crear el backup
echo "Creando backup de '$ORIGEN'..."
tar -czf "${DESTINO}/${ARCHIVO}" "$ORIGEN"

# Eliminar backups de más de 7 días
find "$DESTINO" -name "backup-*.tar.gz" -mtime +7 -delete

echo "Backup completado: ${DESTINO}/${ARCHIVO}"
echo "Tamaño: $(du -sh "${DESTINO}/${ARCHIVO}" | cut -f1)"

Con Bash scripting tienes la capacidad de combinar todo lo visto hasta ahora en automatizaciones reales: conectarse a servidores vía SSH, gestionar permisos, verificar el estado de servicios con systemctl, limpiar logs, hacer backups programados con cron. La línea entre scripting y programación es difusa, y Bash vive justo en esa frontera.