Scripts prácticos: backup automático, monitor del sistema, limpieza de ficheros
Introducción
Esta unidad reúne todos los conceptos de la sección en tres scripts completos y funcionales: un sistema de backup con rotación, un monitor de recursos del sistema y un limpiador de ficheros antiguos. Cada script puede usarse directamente o adaptarse como punto de partida.
Script 1: Backup automático con rotación
Crea copias de seguridad comprimidas con fecha, mantiene solo los últimos N backups y registra todo en un fichero de log.
#!/usr/bin/env bash
# backup.sh — Backup con rotación y log
set -euo pipefail
# ── Configuración ────────────────────────────────────────
readonly ORIGEN="/var/www/html"
readonly DESTINO="/backup"
readonly MAX_BACKUPS=7
readonly LOG="/var/log/backup.log"
readonly FECHA=$(date '+%Y%m%d_%H%M%S')
readonly FICHERO_BACKUP="backup_{$FECHA}.tar.gz"
# ── Funciones de log ─────────────────────────────────────
log() {
local MSG="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
echo "$MSG"
echo "$MSG" >> "$LOG"
}
error() {
log "ERROR: $*"
exit 1
}
# ── Validaciones ─────────────────────────────────────────
[ -d "$ORIGEN" ] || error "El directorio origen no existe: $ORIGEN"
[ -d "$DESTINO" ] || mkdir -p "$DESTINO"
# ── Limpieza al salir ─────────────────────────────────────
TMPFILE=""
cleanup() {
[ -n "$TMPFILE" ] && rm -f "$TMPFILE"
}
trap cleanup EXIT
# ── Crear backup ─────────────────────────────────────────
log "Iniciando backup de $ORIGEN"
TMPFILE=$(mktemp --suffix=.tar.gz)
if tar -czf "$TMPFILE" -C "$(dirname "$ORIGEN")" "$(basename "$ORIGEN")"; then
mv "$TMPFILE" "$DESTINO/$FICHERO_BACKUP"
TMPFILE="" # ya no necesita limpiarse
TAMANIO=$(du -sh "$DESTINO/$FICHERO_BACKUP" | cut -f1)
log "Backup creado: $FICHERO_BACKUP ($TAMANIO)"
else
error "Fallo al crear el backup"
fi
# ── Rotación: eliminar backups antiguos ───────────────────
log "Rotando backups (máximo $MAX_BACKUPS)..."
TOTAL=$(find "$DESTINO" -maxdepth 1 -name "backup_*.tar.gz" | wc -l)
if [ "$TOTAL" -gt "$MAX_BACKUPS" ]; then
ELIMINAR=$((TOTAL - MAX_BACKUPS))
find "$DESTINO" -maxdepth 1 -name "backup_*.tar.gz" \
| sort | head -n "$ELIMINAR" \
| while read -r fichero; do
rm "$fichero"
log "Eliminado backup antiguo: $(basename "$fichero")"
done
fi
log "Backup completado. Total backups: $(find "$DESTINO" -name "backup_*.tar.gz" | wc -l)"
Automatizar con cron:
# Ejecutar a las 2:00 de la madrugada todos los días
0 2 * * * /usr/local/bin/backup.sh
Script 2: Monitor de recursos del sistema
Comprueba CPU, memoria, disco y procesos zombis. Envía alertas a stderr (o a un fichero de log) si algún umbral se supera.
#!/usr/bin/env bash
# monitor.sh — Monitor de recursos con alertas
set -euo pipefail
# ── Umbrales ─────────────────────────────────────────────
readonly UMBRAL_CPU=80 # % de CPU
readonly UMBRAL_MEM=85 # % de memoria
readonly UMBRAL_DISCO=90 # % de disco
readonly LOG="/var/log/monitor.log"
# ── Colores ──────────────────────────────────────────────
ROJO='\033[0;31m'
VERDE='\033[0;32m'
AMARILLO='\033[1;33m'
NC='\033[0m' # sin color
# ── Funciones ────────────────────────────────────────────
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG"
}
alerta() {
echo -e "{$ROJO}[ALERTA]{$NC} $*"
log "ALERTA: $*"
}
ok() {
echo -e "{$VERDE}[OK]{$NC} $*"
}
# ── Comprobaciones ───────────────────────────────────────
comprobar_cpu() {
local USO
# awk calcula el porcentaje de uso: 100 - %idle
USO=$(top -bn1 | grep "Cpu(s)" | awk '{print 100 - $8}' | cut -d. -f1)
if [ "$USO" -ge "$UMBRAL_CPU" ]; then
alerta "CPU al {$USO}% (umbral: {$UMBRAL_CPU}%)"
log " Top procesos por CPU:"
ps aux --sort=-%cpu | head -5 | awk '{printf " %-10s %5s%% %s\n", $1, $3, $11}' | tee -a "$LOG"
else
ok "CPU al {$USO}%"
fi
}
comprobar_memoria() {
local TOTAL DISPONIBLE USO
TOTAL=$(grep MemTotal /proc/meminfo | awk '{print $2}')
DISPONIBLE=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
USO=$(( (TOTAL - DISPONIBLE) * 100 / TOTAL ))
if [ "$USO" -ge "$UMBRAL_MEM" ]; then
alerta "Memoria al {$USO}% (umbral: {$UMBRAL_MEM}%)"
log " Top procesos por memoria:"
ps aux --sort=-%mem | head -5 | awk '{printf " %-10s %5s%% %s\n", $1, $4, $11}' | tee -a "$LOG"
else
ok "Memoria al {$USO}%"
fi
}
comprobar_disco() {
local ALERTA=false
while IFS= read -r linea; do
local USO PUNTO_MONTAJE
USO=$(echo "$linea" | awk '{print $5}' | tr -d '%')
PUNTO_MONTAJE=$(echo "$linea" | awk '{print $6}')
if [ "$USO" -ge "$UMBRAL_DISCO" ]; then
alerta "Disco {$PUNTO_MONTAJE} al {$USO}% (umbral: {$UMBRAL_DISCO}%)"
ALERTA=true
else
ok "Disco {$PUNTO_MONTAJE} al {$USO}%"
fi
done < <(df -h | grep '^/dev' | grep -v tmpfs)
$ALERTA || true
}
comprobar_zombis() {
local ZOMBIS
ZOMBIS=$(ps aux | awk '$8 == "Z"' | wc -l)
if [ "$ZOMBIS" -gt 0 ]; then
alerta "{$ZOMBIS} proceso(s) zombi detectado(s)"
ps aux | awk '$8 == "Z" {print}' | tee -a "$LOG"
else
ok "Sin procesos zombi"
fi
}
# ── Ejecución ────────────────────────────────────────────
echo "=== Monitor de sistema: $(hostname) — $(date) ==="
comprobar_cpu
comprobar_memoria
comprobar_disco
comprobar_zombis
echo "=== Fin del informe ==="
Script 3: Limpieza de ficheros antiguos
Elimina ficheros más antiguos de N días en directorios configurados, con modo de prueba (dry-run) para ver qué se borraría sin hacerlo.
#!/usr/bin/env bash
# limpieza.sh — Limpieza de ficheros antiguos
set -euo pipefail
# ── Uso ──────────────────────────────────────────────────
usage() {
cat <<EOF
Uso: $0 [opciones]
-d DIR Directorio a limpiar (puede usarse varias veces)
-a DIAS Eliminar ficheros más antiguos de DIAS días (por defecto: 30)
-p PATRÓN Patrón de ficheros a eliminar (por defecto: *)
-n Dry-run: mostrar qué se eliminaría sin borrar
-h Mostrar esta ayuda
EOF
exit 0
}
# ── Valores por defecto ───────────────────────────────────
DIRECTORIOS=()
DIAS=30
PATRON="*"
DRY_RUN=false
# ── Argumentos ───────────────────────────────────────────
while getopts "d:a:p:nh" opt; do
case $opt in
d) DIRECTORIOS+=("$OPTARG") ;;
a) DIAS="$OPTARG" ;;
p) PATRON="$OPTARG" ;;
n) DRY_RUN=true ;;
h) usage ;;
?) echo "Opción desconocida: -$OPTARG" >&2; exit 2 ;;
esac
done
# ── Validaciones ─────────────────────────────────────────
if [ {$#DIRECTORIOS[@]} -eq 0 ]; then
echo "Error: se requiere al menos un directorio (-d)" >&2
exit 2
fi
if ! [[ "$DIAS" =~ ^[0-9]+$ ]]; then
echo "Error: DIAS debe ser un número entero positivo" >&2
exit 2
fi
# ── Limpiar un directorio ─────────────────────────────────
limpiar_directorio() {
local DIR="$1"
local ELIMINADOS=0
local ESPACIO_LIBERADO=0
if [ ! -d "$DIR" ]; then
echo "Aviso: el directorio no existe: $DIR" >&2
return
fi
echo "Procesando: $DIR (ficheros con más de $DIAS días, patrón: $PATRON)"
while IFS= read -r -d '' fichero; do
local TAMANIO
TAMANIO=$(du -b "$fichero" 2>/dev/null | cut -f1 || echo 0)
if $DRY_RUN; then
echo " [DRY-RUN] Se eliminaría: $fichero ($(du -sh "$fichero" | cut -f1))"
else
rm -f "$fichero"
echo " Eliminado: $fichero"
fi
((ELIMINADOS++))
((ESPACIO_LIBERADO += TAMANIO))
done < <(find "$DIR" -maxdepth 1 -name "$PATRON" -type f -mtime +"$DIAS" -print0)
echo " Total: $ELIMINADOS ficheros$({$DRY_RUN} && echo " (simulado)" || echo " eliminados")"
if [ "$ESPACIO_LIBERADO" -gt 0 ]; then
echo " Espacio liberado: $((ESPACIO_LIBERADO / 1024 / 1024)) MB"
fi
}
# ── Ejecución ────────────────────────────────────────────
$DRY_RUN && echo "[DRY-RUN activado — no se eliminará nada]"
echo "Inicio: $(date)"
TOTAL_LIBERADO=0
for dir in "{${DIRECTORIOS[@]}}"; do
limpiar_directorio "$dir"
done
echo "Fin: $(date)"
Ejemplos de uso:
# Ver qué se eliminaría (sin borrar nada)
./limpieza.sh -d /tmp -d /var/log -a 30 -p "*.log" -n
# Eliminar realmente
./limpieza.sh -d /tmp -a 7 -p "*.tmp"
# Múltiples directorios y patrones personalizados
./limpieza.sh -d /var/log/nginx -d /var/log/apache2 -a 60 -p "*.gz"
# En cron: limpiar /tmp cada domingo a las 3:00
0 3 * * 0 /usr/local/bin/limpieza.sh -d /tmp -a 7 >> /var/log/limpieza.log 2>&1
Resumen de la sección
A lo largo de esta sección se han cubierto los pilares del scripting bash:
- Variables de entorno, configuración de shell y aliases
- Estructura de un script: shebang, variables, expansiones, aritmética
- Argumentos posicionales,
$@,getopts - Comparaciones con
[ ],[[ ]]y el comandotest - Condicionales
if/elif/else/fiycase/esac -
Bucles
for,while,until,break,continue - Funciones,
local,returny librerías reutilizables - Entrada/salida con
read,echo,printfy here-docs - Manejo de errores con
set -euo pipefail,trapy depuración - Scripts completos: backup, monitorización y limpieza