Un script de shell es un archivo de texto que contiene una serie de comandos que el intérprete ejecuta secuencialmente.
10.1. ¿Por qué scripting en shell?
Si quieres ser un administrador de sistemas, el programar pequeños scripts significa:
- Automatizar tareas repetitivas (copias de seguridad, rotación de logs, creación masiva de usuarios).
- Combinar herramientas ya conocidas (
grep,sed,awk,find) para resolver problemas complejos. - Reducir errores humanos al estandarizar procedimientos.
- Cumplir con los objetivos del examen LFCS, donde se pueden pedir scripts sencillos que realicen determinadas operaciones.
En este capítulo aprenderás a escribir, depurar y ejecutar scripts en Bash, el intérprete por defecto en la mayoría de distribuciones.
10.2. El primer script: estructura y ejecución
10.2.1. El shebang
Todo script comienza con una línea especial que indica qué intérprete usar. Esta línea se conoce como shebang y tiene el formato:
#!/bin/bash
Puede ser también #!/usr/bin/env bash para mayor portabilidad. Sin ella, el script se ejecutará con el shell actual del usuario.
10.2.2. Crear y ejecutar un script
1. Crea un archivo con extensión .sh (aunque no es obligatoria):
nano mi_script.sh
2. Escribe:
#!/bin/bash
echo "Hola, mundo!"
echo "Hoy es $(date)"
3. Dale permisos de ejecución:
chmod +x mi_script.sh
4. Ejecuta:
./mi_script.sh
También se puede ejecutar con bash mi_script.sh sin necesidad de permisos de ejecución, pero entonces no es necesario el shebang.
10.3. Variables y parámetros
10.3.1. Variables de usuario
Se asignan sin espacios alrededor del =:
nombre="Juan"
edad=25
Para usar su valor, se antepone $:
echo "Me llamo $nombre y tengo $edad años."
Si el nombre de la variable puede confundirse con texto adyacente, se usan llaves:
echo "Archivo: ${nombre}.txt"
10.3.2. Variables de entorno
Son heredadas por los procesos hijos. Algunas importantes:
$HOME,$USER,$PATH,$PWD,$UID- Pueden usarse directamente:
echo "Tu home es $HOME"
Para crear una variable de entorno, se exporta:
export MI_VAR="valor"
Normalmente se definen en ~/.bashrc o /etc/profile.
10.3.3. Sustitución de comandos
Permite capturar la salida de un comando y asignarla a una variable o usarla en otra orden.
fecha=$(date +%F)
echo "La fecha es $fecha"
También se puede usar la sintaxis con acentos graves (obsoleta pero aún funcional):
fecha=`date +%F`
10.3.4. Parámetros posicionales
Son los argumentos que se pasan al script:
$0: nombre del script.$1,$2, … : primer argumento, segundo, etc.$#: número de argumentos.$@: todos los argumentos como lista separada.$*: todos los argumentos como una sola cadena (atención al manejo de espacios).$?: código de salida del último comando ejecutado.$$: PID del script actual.
Ejemplo:
#!/bin/bash
echo "Script: $0"
echo "Primer argumento: $1"
echo "Número de argumentos: $#"
echo "Todos los argumentos: $@"
Si se ejecuta ./script.sh hola mundo, se obtiene:
Script: ./script.sh
Primer argumento: hola
Número de argumentos: 2
Todos los argumentos: hola mundo
10.3.5. Lectura interactiva: read
Pide datos al usuario durante la ejecución:
echo -n "Introduce tu nombre: "
read nombre
echo "Hola, $nombre"
Se pueden usar opciones como -p (prompt) y -s (silencioso, para contraseñas):
read -p "Usuario: " usuario
read -sp "Contraseña: " password
echo
10.4. Operaciones aritméticas
Bash soporta aritmética entera. La forma más limpia es con $((...)):
a=10
b=3
suma=$((a + b))
resto=$((a % b))
echo "Suma: $suma, Resto: $resto"
Incrementos y decrementos:
contador=0
((contador++))
echo $contador
10.5. Estructuras de control
10.5.1. Comprobaciones con test y [ ]
El comando test (o sus equivalentes [ ] y [[ ]]) evalúa expresiones y devuelve un código de salida (0 verdadero, 1 falso).
Comparaciones numéricas:
-eq(equal),-ne(not equal),-gt(greater than),-lt,-ge,-le.
Comparaciones de cadenas:
=: iguales.!=: distintas.-z cadena: longitud cero (vacía).-n cadena: longitud no cero.
Comprobaciones de archivos:
-e archivo: existe.-f archivo: es un archivo regular.-d archivo: es un directorio.-r,-w,-x: legible, escribible, ejecutable.-s archivo: tamaño > 0.
Operadores lógicos:
!,-a(AND),-o(OR) dentro de[ ].- Con
[[ ]]se pueden usar&&,||y()para agrupar.
Ejemplos:
if [ "$a" -gt "$b" ]; then
echo "a es mayor que b"
fi
if [ -f /etc/passwd ] && [ -r /etc/passwd ]; then
echo "passwd existe y es legible"
fi
Ventaja de [[ ]]: es más potente (evita problemas de splitting, permite expresiones regulares con =~). En exámenes LFCS, [ ] es suficiente y clásico.
10.5.2. if / then / elif / else / fi
Estructura:
if condicion1; then
comandos
elif condicion2; then
comandos
else
comandos
fi
Ejemplo: determinar si un número es positivo, negativo o cero.
#!/bin/bash
read -p "Introduce un número: " num
if [ $num -gt 0 ]; then
echo "Positivo"
elif [ $num -lt 0 ]; then
echo "Negativo"
else
echo "Cero"
fi
10.5.3. case
Ideal para múltiples opciones discretas.
case $variable in
patrón1)
comandos;;
patrón2)
comandos;;
*)
comandos por defecto;;
esac
Ejemplo: menú simple.
read -p "Elige (start|stop|restart): " opcion
case $opcion in
start)
echo "Iniciando...";;
stop)
echo "Deteniendo...";;
restart)
echo "Reiniciando...";;
*)
echo "Opción no válida";;
esac
10.6. Bucles
10.6.1. for
Recorre una lista:
for variable in lista; do
comandos
done
La lista puede ser literal (1 2 3), una expansión ({1..5}, *.txt), o la salida de un comando.
Ejemplos:
# Listar archivos .txt
for archivo in *.txt; do
echo "Procesando $archivo"
done
# Contar del 1 al 5
for i in {1..5}; do
echo "Número $i"
done
También se puede usar el formato C-like: for ((i=1; i<=5; i++)); do ... done
10.6.2. while
Ejecuta mientras la condición sea verdadera.
while [ condición ]; do
comandos
done
Ejemplo: leer un archivo línea por línea.
while IFS= read -r linea; do
echo "Línea: $linea"
done < archivo.txt
Cuidado con bucles infinitos. Asegurarse de que la condición se vuelva falsa.
10.6.3. until
Lo mismo que while pero ejecuta mientras la condición sea falsa.
until [ condición ]; do
comandos
done
10.6.4. Control de bucles: break y continue
breaksale del bucle.continuesalta a la siguiente iteración.
10.7. Funciones
Agrupan código reutilizable. Se definen antes de usarse.
nombre_funcion() {
comandos
return valor # opcional, 0-255
}
O bien:
function nombre_funcion {
comandos
}
Dentro de la función, los argumentos se reciben como $1, $2, etc. (son locales a la función).
Ejemplo:
#!/bin/bash
saludar() {
echo "Hola, $1"
}
saludar "Mundo"
saludar "Juan"
Variables locales: dentro de una función, usar local para no contaminar el entorno global.
mi_funcion() {
local var="solo dentro"
echo $var
}
10.8. Manejo de archivos y streams en scripts
Se puede redirigir la salida de todo un bloque:
{
echo "Inicio"
comando1
comando2
} > salida.log 2>&1
O usar descriptores de archivo personalizados para escritura/lectura avanzada, aunque no es imprescindible para el nivel LFCS.
Lectura de archivos de configuración: a menudo se leen variables desde un archivo con source o .:
. /etc/mi_script.conf
source /etc/mi_script.conf
Esto ejecuta el archivo como si su contenido estuviera en el script actual.
10.9. Salida y control de errores
10.9.1. Códigos de salida
Todo comando devuelve un código de salida (0 éxito, 1-255 error). Se captura con $?.
En scripts, debemos salir con un código significativo:
exit 0 # éxito
exit 1 # error genérico
Es buena práctica comprobar el éxito de los comandos críticos:
if ! comando; then
echo "Error al ejecutar comando" >&2
exit 1
fi
O de forma más compacta: comando || { echo "Error"; exit 1; }
10.9.2. set para depuración y robustez
Al principio del script se pueden activar opciones de bash:
set -e: sale del script si un comando falla (excepto en condiciones o hasta que se desactive). Útil pero con matices.set -u: trata las variables no definidas como error.set -o pipefail: si un comando en una tubería falla, la tubería devuelve ese código de error (por defecto solo el último).set -x: imprime cada comando tras expandirlo (depuración).set -v: imprime cada línea antes de ejecutarse (más detalle).
Combinación típica:
set -euo pipefail
Pero set -e puede ser problemático. Alternativamente, manejar errores manualmente.
10.9.3. trap para limpieza
Permite ejecutar código cuando el script recibe una señal (incluso salida normal). Útil para eliminar archivos temporales.
trap 'echo "Saliendo..."; rm -f /tmp/tempfile' EXIT
Señales comunes: EXIT (salida normal), INT (Ctrl+C), TERM, HUP.
10.10. Depuración de scripts
Además de set -x, se puede depurar por partes:
- Insertar
echomostrando valores de variables. - Comentar bloques para aislar problemas.
- Ejecutar el script con
bash -x script.sh(sin modificar el archivo).
Ejemplo:
bash -x mi_script.sh
Esto muestra cada línea precedida de + y con las variables expandidas.
Problemas comunes:
- Espacios en asignaciones (
var = valoren lugar devar=valor). - No escapar variables en comillas (
"$var") cuando contienen espacios. - Uso de
[ ]con operadores no soportados (ej.[ $a -gt $b ]si variables vacías dan error). Solución: entrecomillar[ "$a" -gt "$b" ]o usar[[ ]]. - Permisos de ejecución.
- Falsos amigos con rutas (rutas relativas vs absolutas).
10.11. Ejemplos prácticos de administración
10.11.1. Copia de seguridad de un directorio
#!/bin/bash
set -uo pipefail
origen="${1:-/home}"
destino="/backup/$(date +%F)"
mkdir -p "$destino"
tar -czf "$destino/home_backup.tar.gz" "$origen" && \
echo "Copia creada en $destino" || \
echo "Error en la copia" >&2
10.11.2. Creación masiva de usuarios desde un CSV
Supongamos usuarios.csv con formato nombre,apellido.
#!/bin/bash
while IFS=',' read -r nombre apellido; do
usuario="${nombre,,}.${apellido,,}" # minúsculas, separado por punto
if id "$usuario" &>/dev/null; then
echo "Usuario $usuario ya existe"
else
useradd -m -c "$nombre $apellido" "$usuario"
echo "Creado $usuario"
fi
done < usuarios.csv
10.11.3. Monitorización simple de espacio en disco
#!/bin/bash
umbral=80
particiones=$(df -h | awk 'NR>1 && $5+0 > '"$umbral"' {print $1, $5}')
if [ -n "$particiones" ]; then
echo "¡Atención! Particiones por encima del $umbral%:"
echo "$particiones"
else
echo "Todo bajo control"
fi
10.11.4. Rotación de logs casera
#!/bin/bash
log="/var/log/mi_app.log"
if [ -f "$log" ] && [ $(stat --format=%s "$log") -gt 1048576 ]; then
mv "$log" "$log.$(date +%Y%m%d)"
touch "$log"
echo "Log rotado"
fi
10.12. Ejercicios prácticos
Calculadora simple: Crea un script que reciba dos números y una operación (suma, resta, multiplicación, división) y muestre el resultado. Debe validar el número de argumentos y que no se divida por cero.
Validación de entrada: Script que pregunte una IP y compruebe si es una IPv4 válida (4 octetos entre 0 y 255 separados por puntos). Pista: usar
grepcon expresión regular.Monitor de servicios: Escribe un script que lea un archivo de configuración con nombres de servicios (uno por línea). Para cada servicio, compruebe si está activo (con
systemctl is-active) y si no, intente iniciarlo y registre el evento en un log.Backup rotatorio: Crea un script que haga backup de
/etcen/backups/etc-YYYYMMDD.tar.gzy conserve solo los últimos 7 backups, borrando los más antiguos.Menú interactivo: Usa
casey un bucle infinito para ofrecer un menú con opciones: 1) Mostrar fecha, 2) Listar usuarios conectados, 3) Espacio en disco, 4) Salir. Usareadpara la elección.Depuración: Toma un script de los ejercicios anteriores, introduce intencionadamente un error (por ejemplo, variable sin definir o redirección incorrecta). Utiliza
set -xybash -xpara localizarlo y corregirlo.
10.13. Resumen y consejos LFCS
- La estructura básica de un script (shebang, variables, condicionales, bucles, funciones) debe ser memoria muscular.
- Practica la diferencia entre
[ ]y[[ ]], y cuándo entrecomillar variables. - Controla los códigos de salida y usa
trappara scripts robustos. - En el examen, te pueden pedir que escribas un pequeño script que automatice una tarea; la simplicidad y la claridad puntúan más que la complejidad innecesaria.
- Siempre prueba los scripts con diferentes entradas, incluidos casos límite (archivos inexistentes, espacios en nombres).
- Los conocimientos de este capítulo se integran con todo lo anterior:
grep,sed,awk, permisos y variables de entorno. Un buen script es la combinación de todas esas herramientas.
Has completado los fundamentos de scripting en shell. En el próximo capítulo nos adentraremos en el almacenamiento avanzado con LVM, donde aplicarás muchos de estos conceptos para automatizar la gestión de volúmenes lógicos.
Descubre más desde CIBERED
Suscríbete y recibe las últimas entradas en tu correo electrónico.
