Automatización con Scripts Bash: Guía Completa para Administradores de Sistemas
La automatización es uno de los pilares fundamentales de la administración de sistemas moderna. Los scripts Bash nos permiten automatizar tareas repetitivas, reducir errores humanos y optimizar flujos de trabajo complejos. En esta guía completa exploraremos desde los conceptos básicos hasta técnicas avanzadas de scripting.
¿Qué es Bash y por qué es importante?
Bash (Bourne Again Shell) es el intérprete de comandos más utilizado en sistemas Unix/Linux. Desarrollado como una mejora del shell original de Stephen Bourne, Bash combina características de diferentes shells y añade funcionalidades modernas que lo convierten en una herramienta poderosa para la automatización.
Ventajas de la automatización con Bash:
- Consistencia: Los scripts ejecutan tareas exactamente igual cada vez
- Eficiencia: Procesan grandes volúmenes de datos rápidamente
- Disponibilidad: Bash está presente en prácticamente todos los sistemas Unix/Linux
- Flexibilidad: Permite integrar múltiples herramientas del sistema
- Mantenibilidad: Código reutilizable y modificable
Fundamentos de Scripting en Bash
Estructura básica de un script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| #!/bin/bash
# Shebang: indica al sistema qué intérprete usar
# Comentarios: documentan el código
# Variables: almacenan datos
VARIABLE="valor"
# Comandos: ejecutan acciones
echo "Hola, mundo"
# Funciones: agrupan código reutilizable
function mi_funcion() {
echo "Esta es una función"
}
# Llamada a la función
mi_funcion
|
Variables y tipos de datos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| #!/bin/bash
# Variables de texto
NOMBRE="Andrés"
SERVIDOR="web01"
# Variables numéricas
CONTADOR=0
PUERTO=8080
# Arrays
SERVIDORES=("web01" "web02" "db01")
PUERTOS=(80 443 22 3306)
# Variables especiales
echo "Nombre del script: $0"
echo "Primer parámetro: $1"
echo "Todos los parámetros: $@"
echo "Número de parámetros: $#"
echo "PID del proceso: $$"
echo "Código de salida del último comando: $?"
|
Estructuras de control
Condicionales
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| #!/bin/bash
# if-else básico
if [ "$USER" = "root" ]; then
echo "Ejecutándose como root"
else
echo "Usuario normal: $USER"
fi
# Múltiples condiciones
if [ -f "/etc/passwd" ] && [ -r "/etc/passwd" ]; then
echo "El archivo existe y es legible"
elif [ -f "/etc/passwd" ]; then
echo "El archivo existe pero no es legible"
else
echo "El archivo no existe"
fi
# Case statement
case "$1" in
start)
echo "Iniciando servicio..."
;;
stop)
echo "Deteniendo servicio..."
;;
restart)
echo "Reiniciando servicio..."
;;
*)
echo "Uso: $0 {start|stop|restart}"
exit 1
;;
esac
|
Bucles
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| #!/bin/bash
# Bucle for con lista
for servidor in web01 web02 db01; do
echo "Procesando servidor: $servidor"
ping -c 1 "$servidor" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "$servidor está activo"
else
echo "$servidor no responde"
fi
done
# Bucle for con rango
for i in {1..10}; do
echo "Iteración: $i"
done
# Bucle while
CONTADOR=1
while [ $CONTADOR -le 5 ]; do
echo "Contador: $CONTADOR"
CONTADOR=$((CONTADOR + 1))
done
# Bucle until
INTENTOS=0
until ping -c 1 google.com > /dev/null 2>&1; do
echo "Sin conexión a internet. Intento: $((++INTENTOS))"
sleep 5
if [ $INTENTOS -ge 10 ]; then
echo "Error: No se pudo establecer conexión"
exit 1
fi
done
|
Scripts Prácticos para Administración de Sistemas
1. Script de Backup Automatizado
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
| #!/bin/bash
# backup_sistema.sh - Script de backup automatizado
# Autor: Andrés Núñez
set -euo pipefail # Modo estricto: sale en errores, variables no definidas, fallos en pipes
# Configuración
BACKUP_DIR="/backup"
SOURCE_DIRS=("/etc" "/home" "/var/www")
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/backup_$DATE.log"
RETENTION_DAYS=7
# Función de logging
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Función de limpieza
cleanup() {
log "Limpiando backups antiguos..."
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete
log "Limpieza completada"
}
# Función principal de backup
realizar_backup() {
log "Iniciando backup del sistema"
# Crear directorio de backup si no existe
mkdir -p "$BACKUP_DIR"
# Realizar backup de cada directorio
for dir in "${SOURCE_DIRS[@]}"; do
if [ -d "$dir" ]; then
log "Creando backup de: $dir"
tar -czf "$BACKUP_DIR/backup_$(basename $dir)_$DATE.tar.gz" "$dir" 2>/dev/null
if [ $? -eq 0 ]; then
log "Backup de $dir completado exitosamente"
else
log "ERROR: Falló el backup de $dir"
fi
else
log "ADVERTENCIA: Directorio $dir no encontrado"
fi
done
# Limpiar backups antiguos
cleanup
log "Proceso de backup finalizado"
}
# Verificar permisos de root
if [ "$EUID" -ne 0 ]; then
echo "Este script debe ejecutarse como root"
exit 1
fi
# Ejecutar backup
realizar_backup
# Enviar notificación (opcional)
if command -v mail > /dev/null; then
echo "Backup completado en $(hostname) - $DATE" | mail -s "Backup Report" admin@ejemplo.com
fi
|
2. Script de Monitoreo de Sistema
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
| #!/bin/bash
# monitor_sistema.sh - Monitoreo de recursos del sistema
# Autor: Andrés Núñez
# Configuración de umbrales
CPU_THRESHOLD=80
MEM_THRESHOLD=85
DISK_THRESHOLD=90
LOAD_THRESHOLD=2.0
# Colores para output
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
# Función para mostrar alertas
alert() {
local tipo=$1
local mensaje=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
case $tipo in
"CRITICO")
echo -e "${RED}[CRÍTICO]${NC} $timestamp - $mensaje"
;;
"ADVERTENCIA")
echo -e "${YELLOW}[ADVERTENCIA]${NC} $timestamp - $mensaje"
;;
"OK")
echo -e "${GREEN}[OK]${NC} $timestamp - $mensaje"
;;
esac
}
# Monitoreo de CPU
check_cpu() {
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | sed 's/%us,//')
cpu_usage=${cpu_usage%.*} # Convertir a entero
if [ "$cpu_usage" -gt "$CPU_THRESHOLD" ]; then
alert "CRITICO" "Uso de CPU alto: ${cpu_usage}% (umbral: ${CPU_THRESHOLD}%)"
elif [ "$cpu_usage" -gt $((CPU_THRESHOLD - 10)) ]; then
alert "ADVERTENCIA" "Uso de CPU moderado: ${cpu_usage}%"
else
alert "OK" "Uso de CPU normal: ${cpu_usage}%"
fi
}
# Monitoreo de memoria
check_memory() {
local mem_info=$(free | grep Mem)
local total=$(echo $mem_info | awk '{print $2}')
local used=$(echo $mem_info | awk '{print $3}')
local mem_usage=$((used * 100 / total))
if [ "$mem_usage" -gt "$MEM_THRESHOLD" ]; then
alert "CRITICO" "Uso de memoria alto: ${mem_usage}% (umbral: ${MEM_THRESHOLD}%)"
elif [ "$mem_usage" -gt $((MEM_THRESHOLD - 10)) ]; then
alert "ADVERTENCIA" "Uso de memoria moderado: ${mem_usage}%"
else
alert "OK" "Uso de memoria normal: ${mem_usage}%"
fi
}
# Monitoreo de disco
check_disk() {
while read line; do
usage=$(echo $line | awk '{print $5}' | sed 's/%//')
partition=$(echo $line | awk '{print $6}')
if [ "$usage" -gt "$DISK_THRESHOLD" ]; then
alert "CRITICO" "Disco lleno: $partition al ${usage}% (umbral: ${DISK_THRESHOLD}%)"
elif [ "$usage" -gt $((DISK_THRESHOLD - 10)) ]; then
alert "ADVERTENCIA" "Disco con poco espacio: $partition al ${usage}%"
fi
done < <(df -h | grep -E '^/dev/' | grep -v tmpfs)
}
# Monitoreo de carga del sistema
check_load() {
local load_1min=$(uptime | awk '{print $(NF-2)}' | sed 's/,//')
local cores=$(nproc)
local load_per_core=$(echo "scale=2; $load_1min / $cores" | bc)
if (( $(echo "$load_per_core > $LOAD_THRESHOLD" | bc -l) )); then
alert "CRITICO" "Carga del sistema alta: $load_1min (${load_per_core} por core)"
else
alert "OK" "Carga del sistema normal: $load_1min"
fi
}
# Función principal
main() {
echo "=== Monitor de Sistema - $(date) ==="
echo "Host: $(hostname)"
echo "Uptime: $(uptime -p)"
echo ""
check_cpu
check_memory
check_disk
check_load
echo ""
echo "=== Procesos que más CPU consumen ==="
ps aux --sort=-%cpu | head -6
echo ""
echo "=== Procesos que más memoria consumen ==="
ps aux --sort=-%mem | head -6
}
# Ejecutar monitoreo
main
|
3. Script de Gestión de Usuarios
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
| #!/bin/bash
# gestionar_usuarios.sh - Script para gestión automatizada de usuarios
# Autor: Andrés Núñez
set -euo pipefail
# Configuración
USER_CSV="usuarios.csv"
LOG_FILE="/var/log/gestion_usuarios.log"
DEFAULT_SHELL="/bin/bash"
DEFAULT_GROUPS="users"
# Función de logging
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Función para crear usuario
crear_usuario() {
local username=$1
local fullname=$2
local email=$3
local groups=$4
if id "$username" &>/dev/null; then
log "ADVERTENCIA: El usuario $username ya existe"
return 1
fi
# Crear usuario
useradd -m -s "$DEFAULT_SHELL" -c "$fullname" "$username"
# Añadir a grupos adicionales
if [ -n "$groups" ]; then
usermod -a -G "$groups" "$username"
fi
# Generar contraseña temporal
local temp_password=$(openssl rand -base64 12)
echo "$username:$temp_password" | chpasswd
# Forzar cambio de contraseña en primer login
chage -d 0 "$username"
log "Usuario $username creado exitosamente"
echo "Usuario: $username, Contraseña temporal: $temp_password" >> "/tmp/nuevas_cuentas.txt"
}
# Función para eliminar usuario
eliminar_usuario() {
local username=$1
if ! id "$username" &>/dev/null; then
log "ERROR: El usuario $username no existe"
return 1
fi
# Hacer backup del directorio home
if [ -d "/home/$username" ]; then
tar -czf "/backup/user_${username}_$(date +%Y%m%d).tar.gz" "/home/$username"
log "Backup del directorio home de $username creado"
fi
# Eliminar usuario y directorio home
userdel -r "$username" 2>/dev/null || userdel "$username"
log "Usuario $username eliminado"
}
# Función para procesar archivo CSV
procesar_csv() {
if [ ! -f "$USER_CSV" ]; then
log "ERROR: Archivo $USER_CSV no encontrado"
exit 1
fi
# Leer CSV línea por línea (formato: accion,username,fullname,email,groups)
while IFS=',' read -r accion username fullname email groups; do
# Saltar líneas de comentario o vacías
[[ "$accion" =~ ^#.*$ ]] || [ -z "$accion" ] && continue
case "$accion" in
"crear")
crear_usuario "$username" "$fullname" "$email" "$groups"
;;
"eliminar")
eliminar_usuario "$username"
;;
*)
log "ERROR: Acción desconocida: $accion"
;;
esac
done < "$USER_CSV"
}
# Función para mostrar ayuda
mostrar_ayuda() {
cat << EOF
Uso: $0 [OPCIÓN]
Opciones:
-f FILE Procesar archivo CSV de usuarios
-c USER Crear usuario individual
-d USER Eliminar usuario individual
-l Listar usuarios del sistema
-h Mostrar esta ayuda
Formato CSV: accion,username,fullname,email,groups
Ejemplo: crear,jperez,Juan Perez,jperez@empresa.com,sudo,developers
EOF
}
# Función para listar usuarios
listar_usuarios() {
echo "=== Usuarios del Sistema ==="
awk -F: '$3 >= 1000 && $1 != "nobody" {print $1 " (" $5 ")"}' /etc/passwd
}
# Verificar permisos de root
if [ "$EUID" -ne 0 ]; then
echo "Este script debe ejecutarse como root"
exit 1
fi
# Procesar argumentos
while getopts "f:c:d:lh" opt; do
case $opt in
f)
USER_CSV="$OPTARG"
procesar_csv
;;
c)
read -p "Nombre completo: " fullname
read -p "Email: " email
read -p "Grupos adicionales: " groups
crear_usuario "$OPTARG" "$fullname" "$email" "$groups"
;;
d)
eliminar_usuario "$OPTARG"
;;
l)
listar_usuarios
;;
h)
mostrar_ayuda
;;
\?)
echo "Opción inválida: -$OPTARG"
mostrar_ayuda
exit 1
;;
esac
done
# Si no se especificaron opciones, mostrar ayuda
if [ $OPTIND -eq 1 ]; then
mostrar_ayuda
fi
|
Mejores Prácticas en Scripting Bash
1. Manejo de errores robusto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| #!/bin/bash
# Configuración de manejo de errores
set -euo pipefail
# Función de limpieza al salir
cleanup() {
local exit_code=$?
echo "Limpiando recursos..."
# Limpiar archivos temporales, conexiones, etc.
exit $exit_code
}
# Configurar trap para cleanup
trap cleanup EXIT INT TERM
# Función para manejo de errores personalizado
error_handler() {
echo "Error en línea $1: comando '$2' falló con código $3"
exit $3
}
# Configurar trap para errores
trap 'error_handler ${LINENO} "$BASH_COMMAND" $?' ERR
|
2. Validación de entrada
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| #!/bin/bash
# Validar número de argumentos
if [ $# -ne 2 ]; then
echo "Uso: $0 <archivo> <destino>"
exit 1
fi
# Validar que el archivo existe
if [ ! -f "$1" ]; then
echo "Error: El archivo '$1' no existe"
exit 1
fi
# Validar que el directorio destino existe
if [ ! -d "$(dirname "$2")" ]; then
echo "Error: El directorio destino no existe"
exit 1
fi
# Validar formato de email
validar_email() {
local email=$1
if [[ ! "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
echo "Error: Formato de email inválido: $email"
return 1
fi
return 0
}
|
3. Logging estructurado
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| #!/bin/bash
# Configuración de logging
LOG_LEVEL=${LOG_LEVEL:-"INFO"}
LOG_FILE=${LOG_FILE:-"/var/log/mi_script.log"}
# Función de logging con niveles
log() {
local level=$1
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# Niveles: ERROR=1, WARN=2, INFO=3, DEBUG=4
case $level in
ERROR) level_num=1 ;;
WARN) level_num=2 ;;
INFO) level_num=3 ;;
DEBUG) level_num=4 ;;
*) level_num=3; level="INFO" ;;
esac
case $LOG_LEVEL in
ERROR) max_level=1 ;;
WARN) max_level=2 ;;
INFO) max_level=3 ;;
DEBUG) max_level=4 ;;
*) max_level=3 ;;
esac
if [ $level_num -le $max_level ]; then
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
fi
}
# Ejemplos de uso
log INFO "Script iniciado"
log DEBUG "Variable CONFIG_FILE: $CONFIG_FILE"
log WARN "Archivo de configuración no encontrado, usando valores por defecto"
log ERROR "No se pudo conectar a la base de datos"
|
Automatización de Tareas del Sistema
Cron y automatización temporal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # Ejemplo de archivo crontab para automatización
# Editar con: crontab -e
# Backup diario a las 2:00 AM
0 2 * * * /opt/scripts/backup_sistema.sh
# Monitoreo cada 5 minutos
*/5 * * * * /opt/scripts/monitor_sistema.sh
# Limpieza de logs semanalmente (domingos a las 3:00 AM)
0 3 * * 0 /opt/scripts/cleanup_logs.sh
# Reporte mensual el primer día del mes
0 8 1 * * /opt/scripts/reporte_mensual.sh
|
Integración con systemd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # Archivo de servicio: /etc/systemd/system/mi-monitor.service
[Unit]
Description=Monitor de Sistema
After=network.target
[Service]
Type=simple
User=monitor
Group=monitor
ExecStart=/opt/scripts/monitor_continuo.sh
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
|
Herramientas Avanzadas y Técnicas
Procesamiento de texto con awk y sed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| #!/bin/bash
# Analizar logs de Apache
analizar_logs_apache() {
local log_file="/var/log/apache2/access.log"
echo "=== Top 10 IPs que más acceden ==="
awk '{print $1}' "$log_file" | sort | uniq -c | sort -nr | head -10
echo "=== Top 10 páginas más solicitadas ==="
awk '{print $7}' "$log_file" | sort | uniq -c | sort -nr | head -10
echo "=== Códigos de respuesta ==="
awk '{print $9}' "$log_file" | sort | uniq -c | sort -nr
echo "=== Errores 404 ==="
awk '$9 == "404" {print $7}' "$log_file" | sort | uniq -c | sort -nr
}
# Procesar archivos de configuración
actualizar_config() {
local config_file="/etc/mi_app/config.conf"
local backup_file="${config_file}.backup.$(date +%Y%m%d)"
# Crear backup
cp "$config_file" "$backup_file"
# Actualizar configuración con sed
sed -i.bak \
-e 's/^max_connections=.*/max_connections=1000/' \
-e 's/^timeout=.*/timeout=60/' \
-e '/^#debug_mode/s/^#//' \
"$config_file"
echo "Configuración actualizada. Backup en: $backup_file"
}
|
Paralelización de tareas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| #!/bin/bash
# Función para procesar archivo en paralelo
procesar_archivo() {
local archivo=$1
echo "Procesando: $archivo"
# Simular procesamiento
sleep 2
echo "Completado: $archivo"
}
# Exportar función para uso con parallel
export -f procesar_archivo
# Procesar archivos en paralelo (método 1: GNU parallel)
find /datos -name "*.txt" | parallel procesar_archivo
# Procesar archivos en paralelo (método 2: background jobs)
MAX_JOBS=4
procesar_directorio() {
local dir=$1
local job_count=0
for archivo in "$dir"/*.txt; do
if [ -f "$archivo" ]; then
procesar_archivo "$archivo" &
((job_count++))
# Limitar número de trabajos concurrentes
if [ $job_count -ge $MAX_JOBS ]; then
wait # Esperar a que termine algún trabajo
job_count=0
fi
fi
done
wait # Esperar a que terminen todos los trabajos restantes
}
|
Conclusión
La automatización con scripts Bash es una habilidad esencial para cualquier administrador de sistemas. Los scripts bien diseñados no solo ahorran tiempo y reducen errores, sino que también proporcionan consistencia y escalabilidad a las operaciones de TI.
Puntos clave para recordar:
- Planificación: Diseña tus scripts antes de escribir código
- Modularidad: Usa funciones para código reutilizable
- Manejo de errores: Implementa validaciones y manejo robusto de errores
- Documentación: Comenta tu código y mantén logs detallados
- Seguridad: Valida entradas y usa permisos apropiados
- Pruebas: Testa tus scripts en entornos controlados antes de producción
La automatización efectiva es un proceso iterativo. Comienza con scripts simples y ve añadiendo funcionalidad según tus necesidades crezcan. Con práctica y experiencia, podrás crear soluciones de automatización sofisticadas que transformen la eficiencia de tu infraestructura.
Andrés Nuñez - t4ifi