--- title: "Cómo escribir scripts Bash para administrar servidores Linux" description: "Escribe scripts Bash robustos en Ubuntu 24.04: plantilla con modo estricto, alerta de disco, copia con rotación y ejecución con temporizadores systemd." category: "Herramientas de Automatización" language: es source: https://cubepath.com/es/docs/herramientas-de-automatización/scripts-bash-para-administracion-de-servidores updated: 2026-09-25 --- # Cómo escribir scripts Bash para administrar servidores Linux Bash sigue siendo la forma más rápida de automatizar tareas de mantenimiento en un servidor Linux: no requiere instalar nada y tiene acceso directo a todas las herramientas del sistema. El problema es que un script mal escrito falla en silencio, borra lo que no debe o deja datos a medias. En este tutorial escribirás en Ubuntu 24.04 una plantilla segura, dos scripts útiles (una alerta de espacio en disco y una copia de seguridad con rotación) y los programarás con temporizadores de systemd. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. - Un usuario no root con privilegios `sudo`. - Soltura básica con la terminal y un editor como `nano`. ## Paso 1: Instalar ShellCheck y preparar el directorio ShellCheck es un analizador estático que detecta los errores más comunes de Bash: variables sin comillas, comparaciones incorrectas o comandos que no hacen lo que parece. Instálalo desde los repositorios de Ubuntu: ```bash sudo apt update sudo apt install shellcheck ``` Comprueba la versión: ```bash shellcheck --version ``` ```output ShellCheck - shell script analysis tool version: 0.9.0 ``` Los scripts de administración que ejecuta root se guardan en `/usr/local/sbin`, que ya está en el `PATH` de root y no lo tocan los paquetes del sistema. No necesitas crear nada: el directorio existe por defecto. ## Paso 2: Crear una plantilla con modo estricto Por defecto Bash continúa aunque un comando falle y trata las variables no definidas como cadenas vacías. Un `rm -rf "$DIR/"` con `DIR` sin definir es el ejemplo clásico de desastre. Estas tres opciones cambian ese comportamiento: - `set -e`: el script termina en cuanto un comando devuelve un código de error. - `set -u`: usar una variable no definida es un error. - `set -o pipefail`: una tubería falla si falla cualquiera de sus comandos, no solo el último. Esta es la plantilla que usarán los scripts de la guía. Registra los mensajes en el journal del sistema con `logger`, de modo que puedes consultarlos después con `journalctl`: ```bash #!/usr/bin/env bash set -euo pipefail SCRIPT_NAME="$(basename "$0")" readonly SCRIPT_NAME log() { # Escribe en stderr y en el journal con la etiqueta del script echo "$*" >&2 logger -t "$SCRIPT_NAME" -- "$*" } on_error() { log "ERROR: fallo en la línea $1" } trap 'on_error $LINENO' ERR main() { log "Inicio" # Tu lógica aquí log "Fin" } main "$@" ``` La función `main` al final y `"$@"` entre comillas hacen que los argumentos lleguen intactos, aunque contengan espacios. El `trap ... ERR` avisa de en qué línea falló el script antes de que `set -e` lo termine. ## Paso 3: Escribir una alerta de espacio en disco Un disco lleno detiene bases de datos, colas de correo y logs. Este script revisa cada sistema de archivos real y escribe un aviso en el journal cuando el uso supera un umbral. Crea el archivo: ```bash sudo nano /usr/local/sbin/disk-alert ``` Pega el contenido: ```bash #!/usr/bin/env bash set -euo pipefail readonly SCRIPT_NAME="disk-alert" readonly THRESHOLD="${1:-85}" if ! [[ "$THRESHOLD" =~ ^[0-9]+$ ]] || (( THRESHOLD < 1 || THRESHOLD > 100 )); then echo "Uso: $0 [umbral_porcentaje]" >&2 exit 2 fi status=0 # Ignora sistemas de archivos virtuales y los montajes de snap while read -r pcent target; do usage="${pcent%\%}" if (( usage >= THRESHOLD )); then logger -t "$SCRIPT_NAME" -p user.warning -- "$target al ${usage}% (umbral ${THRESHOLD}%)" echo "AVISO: $target al ${usage}%" >&2 status=1 fi done < <(df --output=pcent,target -x tmpfs -x devtmpfs -x squashfs -x overlay -x efivarfs | tail -n +2) exit "$status" ``` Algunos detalles importantes: - `${1:-85}` usa 85 como umbral si no se pasa ningún argumento, sin romper `set -u`. - El argumento se valida antes de usarlo en una comparación aritmética. - `df --output` genera columnas limpias, sin necesidad de recortar con `awk`. - El script sale con código 1 cuando hay algún aviso, así systemd marca la ejecución como fallida y es fácil detectarla. Hazlo ejecutable y pásale ShellCheck: ```bash sudo chmod 755 /usr/local/sbin/disk-alert shellcheck /usr/local/sbin/disk-alert ``` Si ShellCheck no muestra nada, no ha encontrado problemas. Para probar el aviso, ejecútalo con un umbral del 1 %, que siempre se superará: ```bash sudo disk-alert 1; echo "código de salida: $?" ``` ```output AVISO: / al 23% AVISO: /boot al 12% código de salida: 1 ``` Comprueba que el mensaje ha llegado al journal: ```bash journalctl -t disk-alert -n 5 --no-pager ``` ```output sep 25 10:14:02 servidor disk-alert[4121]: / al 23% (umbral 1%) sep 25 10:14:02 servidor disk-alert[4122]: /boot al 12% (umbral 1%) ``` ## Paso 4: Escribir una copia de seguridad con rotación El siguiente script empaqueta los directorios que indiques en un archivo `.tar.gz` con fecha y borra las copias con más de 14 días. Crea el directorio de destino con permisos solo para root, ya que las copias de `/etc` contienen datos sensibles: ```bash sudo install -d -m 700 /var/backups/local ``` Crea el script: ```bash sudo nano /usr/local/sbin/local-backup ``` ```bash #!/usr/bin/env bash set -euo pipefail readonly SCRIPT_NAME="local-backup" readonly DEST="/var/backups/local" readonly KEEP_DAYS=14 readonly SOURCES=(/etc /var/www) log() { echo "$*" >&2 logger -t "$SCRIPT_NAME" -- "$*" } trap 'log "ERROR: la copia ha fallado en la línea $LINENO"' ERR main() { local stamp archive tmp stamp="$(date +%Y%m%d-%H%M%S)" archive="$DEST/backup-$stamp.tar.gz" tmp="$archive.part" # Incluye solo los orígenes que existen local existing=() for src in "${SOURCES[@]}"; do if [[ -e "$src" ]]; then existing+=("$src") fi done log "Creando $archive" tar --create --gzip --file "$tmp" --directory / "${existing[@]#/}" mv "$tmp" "$archive" log "Borrando copias con más de $KEEP_DAYS días" find "$DEST" -maxdepth 1 -name 'backup-*.tar.gz' -mtime +"$KEEP_DAYS" -print -delete log "Copia completada: $(du -h "$archive" | cut -f1)" } main "$@" ``` El archivo se escribe primero con la extensión `.part` y solo se renombra cuando `tar` termina bien. Así una copia interrumpida nunca parece válida, y la rotación con `find` solo toca archivos con el patrón `backup-*.tar.gz` dentro de `DEST`. Ajusta `SOURCES` a los directorios que quieras guardar. Dale permisos, revísalo y ejecútalo: ```bash sudo chmod 755 /usr/local/sbin/local-backup shellcheck /usr/local/sbin/local-backup sudo local-backup ``` ```output Creando /var/backups/local/backup-20260925-101830.tar.gz Borrando copias con más de 14 días Copia completada: 1,2M ``` Comprueba el contenido del archivo: ```bash sudo tar --list --file /var/backups/local/backup-*.tar.gz | head -n 5 ``` ```output etc/ etc/hostname etc/fstab etc/hosts etc/passwd ``` Las rutas se guardan sin la barra inicial (`--directory /` y `${existing[@]#/}`), así que al restaurar con `tar --extract` los archivos se descomprimen en el directorio actual y no sobrescriben el sistema por accidente. > Importante: una copia guardada en el mismo disco no te protege si el servidor se pierde. Cópiala después a otro sistema, por ejemplo con `rsync` sobre SSH o a un almacenamiento de objetos. ## Paso 5: Programar los scripts con temporizadores de systemd Los temporizadores de systemd tienen ventajas sobre cron para tareas de administración: registran la salida en el journal automáticamente, evitan ejecuciones solapadas y, con `Persistent=true`, recuperan una ejecución perdida si el servidor estaba apagado. Crea la unidad de servicio de la copia de seguridad: ```bash sudo nano /etc/systemd/system/local-backup.service ``` ```ini [Unit] Description=Copia de seguridad local [Service] Type=oneshot ExecStart=/usr/local/sbin/local-backup Nice=10 IOSchedulingClass=idle ``` Crea el temporizador, que la lanzará cada día a las 03:00: ```bash sudo nano /etc/systemd/system/local-backup.timer ``` ```ini [Unit] Description=Copia de seguridad local diaria [Timer] OnCalendar=*-*-* 03:00:00 Persistent=true RandomizedDelaySec=10min [Install] WantedBy=timers.target ``` Repite el patrón para la alerta de disco, cada 15 minutos. Primero el servicio: ```bash sudo nano /etc/systemd/system/disk-alert.service ``` ```ini [Unit] Description=Alerta de espacio en disco [Service] Type=oneshot ExecStart=/usr/local/sbin/disk-alert 85 ``` Y después el temporizador: ```bash sudo nano /etc/systemd/system/disk-alert.timer ``` ```ini [Unit] Description=Comprobar el espacio en disco cada 15 minutos [Timer] OnCalendar=*:0/15 [Install] WantedBy=timers.target ``` Recarga systemd y activa los dos temporizadores: ```bash sudo systemctl daemon-reload sudo systemctl enable --now local-backup.timer disk-alert.timer ``` Comprueba cuándo se ejecutarán: ```bash systemctl list-timers local-backup.timer disk-alert.timer ``` ```output NEXT LEFT LAST PASSED UNIT ACTIVATES Thu 2026-09-25 10:30:00 UTC 9min left - - disk-alert.timer disk-alert.service Fri 2026-09-26 03:04:12 UTC 16h left - - local-backup.timer local-backup.service 2 timers listed. ``` Puedes lanzar una ejecución manual en cualquier momento y revisar su resultado: ```bash sudo systemctl start local-backup.service journalctl -u local-backup.service -n 10 --no-pager ``` ## Solución de problemas **`unbound variable`**: con `set -u`, leer `$1` sin argumentos o una variable no definida es un error. Usa un valor por defecto, como `${1:-}` o `${VAR:-valor}`. **`/usr/bin/env: 'bash\r': No such file or directory`**: el script se editó en Windows y tiene finales de línea CRLF. Conviértelos con `sudo sed -i 's/\r$//' /usr/local/sbin/disk-alert`. **`Permission denied` al ejecutar el script**: falta el bit de ejecución. Revísalo con `ls -l` y aplica `sudo chmod 755` al archivo. **El servicio aparece como `failed`**: consulta el motivo con `systemctl status disk-alert.service` y `journalctl -u disk-alert.service`. En el caso de `disk-alert`, un estado `failed` significa que algún disco supera el umbral, que es justo lo que quieres detectar. ## Conclusión Has creado una plantilla de Bash con modo estricto y registro en el journal, una alerta de espacio en disco y una copia de seguridad con rotación segura, y las has programado con temporizadores de systemd. Pasar ShellCheck a cada script antes de ponerlo en producción evita la mayoría de los errores habituales. Como siguientes pasos puedes: - Enviar las copias a otro servidor con `rsync` sobre SSH y una clave dedicada. - Recibir los avisos por correo o en un canal de chat añadiendo `OnFailure=` a la unidad del servicio. - Pasar a Ansible cuando tengas que aplicar los mismos scripts en varios servidores.