Objetivo:
Comprender la modularización y reutilización de código en scripts de Linux mediante la creación, exportación y gestión de funciones en Bash, aplicando buenas prácticas de control de errores, paso de argumentos y códigos de salida (exit status).
Escenario:
- Herramientas: Terminal Linux, editores de texto (nano), Bash shell, estructuras condicionales y de bucle.
- Conceptos analizados: Funciones en Bash, ámbito de las variables (
local), paso de parámetros posicionales ($1,$2), gestión de códigos de retorno (return) y librerías de scripts. - Proceso clave: Desarrollo de un script de automatización modular para realizar tareas comunes de mantenimiento (copias de seguridad, comprobación de espacio y creación de usuarios) estructurado mediante funciones reutilizables.
Escenario Real: Centralización de herramientas de administración
Como administrador de sistemas, te enfrentas a scripts monolíticos kilométricos donde repetir código de comprobación de errores es habitual y su mantenimiento se vuelve caótico. Tu objetivo es refactorizar un script de automatización dividiéndolo en funciones limpias y autodenominadas, permitiendo centralizar la lógica de negocio y reutilizar bloques de código de forma eficiente.
NOTA: Para realizar esta práctica, crearemos un directorio de trabajo en tu espacio personal (por ejemplo,
~/scripts_modular) donde iremos diseñando, probando y depurando los diferentes componentes paso a paso.Instrucciones:
Fase 1: Estructura básica de una función y paso de parámetros:
- Crear un directorio de trabajo y un fichero de pruebas:
mkdir -p ~/scripts_modular && cd ~/scripts_modular - Crear un script básico llamado
test_funciones.shconnano test_funciones.sh. - Añadir la declaración y llamada de una función que salude recibiendo argumentos:
#!/bin/bash # Declaración de la función saludar saludar() { local nombre="$1" echo "¡Hola, $nombre! Bienvenido al sistema de administración." } # Llamada a la función pasando un parámetro saludar "Carlos" saludar "Ana" - Guardar el fichero, darle permisos de ejecución (
chmod +x test_funciones.sh) y ejecutarlo:./test_funciones.sh
Fase 2: Creación de funciones con retorno de códigos de estado (Exit Status):
- Modificar o crear un nuevo script
check_root.shpara comprobar si el usuario que ejecuta el script tiene privilegios de superusuario mediante una función que devuelva un código de error:#!/bin/bash verificar_root() { if [ "$EUID" -ne 0 ]; then echo "[ERROR]: Este script debe ejecutarse con privilegios de root." >&2 return 1 fi echo "[OK]: Privilegios de administrador confirmados." return 0 } # Uso de la función evaluando su código de salida verificar_root if [ $? -ne 0 ]; then echo "Saliendo del programa..." exit 1 else echo "Continuando con la ejecución normal de tareas..." fi - Probar la ejecución tanto con
./check_root.shcomo consudo ./check_root.shpara observar el cambio de comportamiento.
Fase 3: Desarrollo de un script automatizador modular de mantenimiento:
- Crear un script avanzado de automatización llamado
mantenimiento_pro.shque agrupe varias funciones de utilidad real (comprobación de espacio libre, creación segura de directorios y registro en log):#!/bin/bash # Fichero de registro (Log) LOG_FILE="/tmp/mantenimiento.log" # Función de registro formateado log_mensaje() { local nivel="$1" local mensaje="$2" echo "$(date '+%Y-%m-%d %H:%M:%S') [$nivel] - $mensaje" | tee -a "$LOG_FILE" } # Función para comprobar espacio en disco de la partición raíz comprobar_espacio() { local uso_raiz uso_raiz=$(df / | awk 'NR==2 {print $5}' | tr -d '%') log_mensaje "INFO" "El uso actual de la partición raíz es del ${uso_raiz}%" if [ "$uso_raiz" -gt 80 ]; then log_mensaje "ALERTA" "¡Espacio en disco bajo mínimos!" return 1 fi return 0 } # Flujo principal del script log_mensaje "INFO" "=== INICIO DE TAREAS DE MANTENIMIENTO ===" comprobar_espacio log_mensaje "INFO" "=== FIN DE TAREAS DE MANTENIMIENTO ===" - Guardar el script, otorgarle permisos de ejecución y lanzarlo para comprobar la generación automática del fichero de log en
/tmp/mantenimiento.log.
Fase 4: Modularización avanzada (Librerías de funciones externas):
- Crear un fichero independiente que actúe como librería de funciones llamado
funciones_comunes.shconteniendo herramientas reutilizables (ej.log_mensaje). - Crear un segundo script (ej.
app_principal.sh) que importe la librería utilizando el comandosourceo el punto (.):#!/bin/bash # Importar la librería de funciones externas source ./funciones_comunes.sh log_mensaje "INFO" "Ejecutando script principal importando funciones externas." - Verificar que el script principal es capaz de invocar perfectamente las funciones declaradas en el fichero externo.
Verificación:
- Comprobar la correcta ejecución de los scripts sin errores de sintaxis utilizando el modo de depuración de Bash:
bash -x test_funciones.sh - Revisar el contenido generado en el fichero de log de pruebas para asegurar que los mensajes se registran con el formato de fecha y nivel adecuados:
cat /tmp/mantenimiento.log
Posibles errores y resolución de problemas:
Si al invocar una función dentro de tu script observas que las variables modificadas dentro de ella alteran inesperadamente el valor de variables globales con el mismo nombre en el resto del script, se debe a un fallo en el ámbito de las variables.
Solución: Asegúrate de declarar siempre las variables internas de tus funciones anteponiendo la palabra reservada
local(ej.local mi_variable="valor"), evitando así contaminar el espacio de nombres global del script.
Alternativa directa: Depuración interactiva de funciones complejas
Si una función no se comporta como esperas y necesitas seguir paso a paso su ejecución y la evaluación de sus parámetros posicionales, puedes invocar la ejecución del script activando el flag de rastreo detallado:
bash -v -x mantenimiento_pro.sh
Preguntas de reflexión:
- ¿Por qué es una buena práctica utilizar la palabra clave
localal declarar variables dentro de una función en Bash? - ¿Qué diferencia existe entre el valor devuelto por una sentencia
returndentro de una función de Bash frente al uso deechopara "retornar" resultados? - ¿Cómo gestiona Bash el paso de argumentos a una función y en qué se diferencia de cómo se reciben los parámetros en un script completo?
- ¿Qué ventajas aporta separar funciones comunes en un fichero de librería independiente para importarlo mediante
sourcefrente a duplicar el código en varios scripts?
Haz clic aquí para ver las soluciones y explicaciones
1. ¿Por qué es una buena práctica utilizar la palabra clave local al declarar variables dentro de una función en Bash?
Por defecto, en Bash todas las variables son globales. Si una función modifica una variable sin el modificador local, puede sobreescribir accidentalmente una variable del mismo nombre en el flujo principal del script, generando errores difíciles de depurar (efectos colaterales no deseados).
2. ¿Qué diferencia existe entre el valor devuelto por una sentencia return dentro de una función de Bash frente al uso de echo para "retornar" resultados?
El comando return en una función de Bash solo permite devolver un código de estado numérico (entre 0 y 255) que indica éxito o tipo de error (equivalente a exit status). Si se necesita devolver cadenas de texto o datos complejos generados dentro de la función, se utiliza echo para imprimir el resultado por la salida estándar y capturarlo mediante sustitución de comandos (variable=$(mi_funcion)).
3. ¿Cómo gestiona Bash el paso de argumentos a una función y en qué se diferencia de cómo se reciben los parámetros en un script completo?
Siguen un mecanismo muy similar basado en variables posicionales, pero con un matiz clave: cuando se llama a una función, los parámetros pasados ($1, $2, etc.) reemplazan temporalmente a los parámetros posicionales del script principal mientras se ejecuta la función. El nombre del script original ($0) se mantiene intacto.
4. ¿Qué ventajas aporta separar funciones comunes en un fichero de librería independiente para importarlo mediante source frente a duplicar el código en varios scripts?
Aplica el principio de diseño DRY (Don't Repeat Yourself). Permite centralizar la corrección de errores y la mejora de funcionalidades en un único punto; cualquier cambio realizado en la librería se propagará automáticamente a todos los scripts que la importen, facilitando enormemente el mantenimiento a gran escala.