
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

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

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