Llegamos al hito del módulo, y a una promesa que arrastramos desde 04-02. informe-diario.sh es hoy un programa robusto: valida su entorno, se protege con flock, aborta con diagnóstico, valida texto con regex y dirige su salida con precisión. Pero es también un único fichero de varios cientos de líneas donde conviven log_info, morir, validar_entorno y porcentaje —funciones que no tienen nada que ver con informes y que cualquier otro script del toolkit necesitaría—. La tentación, cuando escribas archivar-historico.sh la semana que viene, será copiar y pegar. Y ahí empieza el deterioro: dos copias que divergen, un fallo corregido en una y no en la otra, tres convenios de registro distintos. En esta lección conviertes ese fichero en un proyecto con arquitectura.
Contenido
sourcefrente a ejecutar- Qué es una librería en Bash
- Localizar la librería sin que las rutas te traicionen
- Guardas de inclusión
- Nombres: qué es público y qué es privado
- Ficheros de configuración
- Orden de precedencia
- La estructura final del toolkit
lib/comun.shinforme-diario.shcomo orquestador- Probar la librería a mano
source frente a ejecutar
source frente a ejecutarEn 03-01 viste las formas de ejecutar un script. La que ahora importa es la que no crea un proceso: frente a ./script.sh o bash script.sh, que lanzan un proceso nuevo del que nada vuelve, source script.sh (o su forma POSIX . script.sh) ejecuta el fichero en este shell.
| Ejecutar | source |
|
|---|---|---|
| Proceso | Uno nuevo (fork + exec) | El actual |
| Variables y funciones que define | Mueren con el hijo | Quedan disponibles |
cd que haga |
No te afecta | Te cambia de directorio |
Necesita permiso +x y shebang |
Sí | No |
exit dentro |
Termina el script | Termina tu shell |
Las dos últimas filas explican por qué una librería no es un script normal. Como se carga con source, no necesita ni shebang ejecutable ni permiso de ejecución. Y sobre todo: un exit en una librería mata al que la carga, incluida tu sesión interactiva si la estabas probando; dentro de una librería se usa return. Esto no es nuevo: es el mismo mecanismo por el que ~/.bashrc define funciones que tu shell conoce (01-02). Una librería es un .bashrc para tus scripts.
- Qué es una librería en Bash
Una librería es un fichero que contiene solo definiciones: funciones, constantes y, como mucho, valores por defecto. Nada que actúe por sí mismo.
# lib/comun.sh — CORRECTO: solo define
log_info() { printf '%s [INFO] %s\n' "$(date '+%F %T')" "$*" >&2; }
# lib/malo.sh — INCORRECTO: todo esto ACTÚA al cargarse
log_info "cargando librería" # ensucia la salida de quien la use
set -euo pipefail # IMPONE su política a quien la carga
cd /srv/veloz # cambia el directorio del que la cargaLa regla es que cargar la librería debe ser silencioso y sin efectos. El set -euo pipefail dentro de una librería es especialmente traicionero: al hacer source se aplica al shell que la carga, así que puede cambiar el comportamiento de errores de un script que no lo esperaba. La política estricta la decide el ejecutable, no la librería. Convenciones: extensión .sh (o .bash), sin shebang ejecutable —opcionalmente un #!/usr/bin/env bash como pista para editores y para shellcheck, pero sin permiso +x—, y una cabecera de comentario que diga qué ofrece.
- Localizar la librería sin que las rutas te traicionen
Aquí está el problema técnico de verdad. ¿Cómo encuentra bin/informe-diario.sh a lib/comun.sh?
source lib/comun.sh # MAL: relativo al directorio ACTUAL, no al script
source ~/veloz-ops/lib/comun.sh # Funciona, pero solo para ti y en tu $HOMELas rutas relativas se resuelven desde el directorio de trabajo actual, que es donde esté quien lanza el script, no donde vive el script. Como ~/veloz-ops/bin está en el PATH (lo pusimos ahí en 01-02), lo normal es invocarlo desde cualquier sitio, y el source falla. El idioma canónico es este:
BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"; readonly BASE_DIR
source "$BASE_DIR/lib/comun.sh"Desmontado de dentro afuera:
${BASH_SOURCE[0]}es la ruta del fichero en ejecución. Se usa en lugar de$0por dos motivos:$0valebashcuando el script se invoca comobash script.sh, y dentro de un fichero cargado consource,$0sigue siendo el del script principal mientras queBASH_SOURCE[0]es la librería. Para una librería que necesita saber dónde está,$0siempre da la respuesta equivocada.dirnameda el directorio que la contiene (04-04) y/..sube a la raíz del proyecto;cd ... && pwdconvierte esa ruta, que puede ser relativa (./bin/..), en absoluta y normalizada; y todo va entre comillas, por si la ruta contiene un espacio.
Con eso, el script funciona invocado como ./bin/informe-diario.sh, como ~/veloz-ops/bin/informe-diario.sh, desde el PATH o desde cron con cualquier directorio de trabajo. Si además quieres seguir enlaces simbólicos hasta el fichero real, readlink -f (05-01) sobre ${BASH_SOURCE[0]} antes del dirname lo resuelve.
- Guardas de inclusión
Si informe-diario.sh carga comun.sh y también informe.sh, que a su vez carga comun.sh, el fichero se procesa dos veces. Redefinir funciones es inofensivo, pero un readonly duplicado provoca un error fatal bajo modo estricto (VELOZ_VERSION: variable de solo lectura). La solución es la misma idea que los include guards de C:
El return 0 a nivel superior de un fichero cargado con source es legal y significa "termina de leer este fichero". El :- es obligatorio porque el script que la carga tiene set -u (05-03) y la variable aún no existe la primera vez. El guion bajo inicial marca la variable como interna.
- Nombres: qué es público y qué es privado
Bash no tiene espacios de nombres: todo vive en un único ámbito global. Si tu librería define log() y el script del compañero también, la última cargada gana silenciosamente. El remedio es un prefijo:
veloz::log_info() { ... } # estilo con dos puntos dobles
veloz_log_info() { ... } # estilo con guion bajo, más portable
_veloz_normaliza() { ... } # guion bajo inicial: uso INTERNO, no lo llamesBash permite :: en nombres de función y queda muy legible, pero no es POSIX y algunas herramientas antiguas se atragantan; veloz_ es la opción segura. Elige uno y sé consistente. La distinción público/privado es por convenio, no la impone el intérprete: el guion bajo inicial es una promesa de "esto puede cambiar sin aviso, no dependas de ello". Lo que sí puedes controlar de verdad es el ámbito de las variables: toda variable dentro de una función va con local (04-02), y así no contamina al script que la usa.
- Ficheros de configuración
~/veloz-ops/etc/veloz-ops.conf existe desde el principio del curso con permisos 600. Su forma más simple es un fichero de asignaciones (VELOZ_CSV=/srv/veloz/datos/envios.csv, VELOZ_LOG_NIVEL=info, VELOZ_CIUDADES="Valencia Sevilla Bilbao Madrid") que se carga con [[ -r "$BASE_DIR/etc/veloz-ops.conf" ]] && source "$BASE_DIR/etc/veloz-ops.conf". Es cómodo —admite comentarios, comillas, incluso $(...)— y tiene un riesgo que debes conocer: source ejecuta código arbitrario. Una línea rm -rf ~ en ese fichero se ejecuta con tus permisos. Por eso los 600 no son decorativos: si alguien puede escribir en tu fichero de configuración, puede ejecutar lo que quiera como tú. Las implicaciones completas se tratan en 08-03.
La alternativa segura es parsear clave=valor sin ejecutar nada, usando lo aprendido en 05-04:
# _veloz_cargar_conf — Carga clave=valor sin ejecutar código. Uso: ... <fichero>
_veloz_cargar_conf() {
local fichero="${1:?}" clave valor linea
[[ -r "$fichero" ]] || return 0
while IFS= read -r linea; do
[[ "$linea" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]] || continue
clave="${BASH_REMATCH[1]}"; [[ "$clave" == VELOZ_* ]] || continue # solo lo nuestro
valor="${BASH_REMATCH[2]}"; valor="${valor%\"}"; valor="${valor#\"}"
printf -v "$clave" '%s' "$valor" # asignación indirecta
done < "$fichero"
}Aquí se juntan tres módulos: la regex con BASH_REMATCH de 05-04 (que además descarta comentarios y líneas vacías al no encajar), las expansiones ${v%\"} de 04-04 y el bucle while read de 04-01. El filtro VELOZ_* impide que el fichero redefina PATH o HOME, y printf -v asigna a la variable cuyo nombre está en $clave: la forma segura de hacer asignación indirecta sin eval.
- Orden de precedencia
Cerramos aquí el diseño de opciones empezado en 03-05. Cuando el mismo valor puede venir de cuatro sitios, hace falta un orden explícito y documentado, de menor a mayor prioridad:
| Nivel | Origen | Ejemplo | Gana sobre |
|---|---|---|---|
| 1 | Valor por defecto en el código | VELOZ_CSV=/srv/veloz/datos/envios.csv |
Nada |
| 2 | Fichero de configuración | etc/veloz-ops.conf |
El defecto |
| 3 | Variable de entorno | VELOZ_CSV=/tmp/x.csv informe-diario.sh |
Los anteriores |
| 4 | Opción de línea de comandos | --csv /tmp/x.csv |
Todos |
El principio: cuanto más cerca del usuario y del momento de la ejecución, más prioridad. Implementarlo es sencillo si respetas el orden de carga, y : "${VAR:=valor}" es el idioma para "asigna solo si está vacía o no definida" —el := asigna, a diferencia del :- que solo sustituye, y el : inicial es el comando nulo—, de modo que una variable de entorno ya definida sobrevive:
: "${VELOZ_CSV:=/srv/veloz/datos/envios.csv}" # 1. defecto, solo si no existe
_veloz_cargar_conf "$BASE_DIR/etc/veloz-ops.conf" # 2. config (respeta lo ya definido)
# 3. el entorno ya estaba puesto antes de arrancar el script
while [[ $# -gt 0 ]]; do case "$1" in # 4. opciones (03-05)
--csv) VELOZ_CSV="$2"; shift 2 ;; *) break ;;
esac; done
- La estructura final del toolkit
~/veloz-ops/ ├── bin/ # ejecutables (+x, con shebang). Están en el PATH │ ├── informe-diario.sh │ └── archivar-historico.sh ├── lib/ # librerías (sin +x, sin shebang, solo definiciones) │ ├── comun.sh # registro, errores, validaciones, utilidades │ └── informe.sh # lógica específica de informes ├── etc/veloz-ops.conf # configuración (permisos 600) └── logs/ # salida: informes, trazas, ficheros de bloqueo
La regla de reparto en una frase: en bin/ va lo que se invoca, en lib/ lo que se reutiliza, en etc/ lo que cambia entre máquinas y en logs/ lo que se genera. Es la misma lógica del FHS que viste en 01-03, aplicada a escala de proyecto, y por eso resulta familiar a cualquiera que abra el repositorio. Un criterio práctico para decidir dónde va una función: si la necesitaría un script que no tuviera nada que ver con informes, va a comun.sh; si solo tiene sentido hablando de envíos y ciudades, va a informe.sh.
lib/comun.sh
lib/comun.sh#!/usr/bin/env bash
# comun.sh — Utilidades compartidas del toolkit de Veloz Envíos.
# Cargar con: source "$BASE_DIR/lib/comun.sh"
[[ -n "${_COMUN_SH:-}" ]] && return 0
readonly _COMUN_SH=1 VELOZ_VERSION='1.0'
readonly _VELOZ_RE_FECHA='^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$'
# veloz_log_info — Mensaje informativo a stderr. Uso: veloz_log_info <texto...>
veloz_log_info() {
[[ "${VELOZ_LOG_NIVEL:-info}" == silencioso ]] && return 0
printf '%s [INFO] %s\n' "$(date '+%F %T')" "$*" >&2
}
# veloz_log_error — Mensaje de error a stderr. Uso: veloz_log_error <texto...>
veloz_log_error() { printf '%s [ERROR] %s\n' "$(date '+%F %T')" "$*" >&2; }
# veloz_morir — Error fatal y salida. Uso: veloz_morir <código> <texto...>
veloz_morir() { local c="${1:?}"; shift; veloz_log_error "$*"; exit "$c"; }
# veloz_validar_fecha — ¿Es AAAA-MM-DD y existe? Uso: veloz_validar_fecha <fecha>
veloz_validar_fecha() { [[ "${1:-}" =~ $_VELOZ_RE_FECHA ]] && date -d "$1" &>/dev/null; }
# veloz_porcentaje — Calcula a/b*100 con 2 decimales. Uso: veloz_porcentaje <a> <b>
veloz_porcentaje() {
(( ${2:?} == 0 )) && { printf '0.00\n'; return 0; }
bc -l <<< "scale=2; ${1:?} * 100 / $2"
}
# veloz_validar_entorno — Comprueba dependencias y rutas. Uso: veloz_validar_entorno
veloz_validar_entorno() {
local cmd
for cmd in bc date find flock; do
command -v "$cmd" > /dev/null || veloz_morir 69 "Falta la utilidad: $cmd"
done
[[ -r "${VELOZ_CSV:?sin definir}" ]] || veloz_morir 66 "No puedo leer $VELOZ_CSV"
mkdir -p "${VELOZ_LOG_DIR:?}" || veloz_morir 73 "No puedo crear $VELOZ_LOG_DIR"
}Fíjate en lo que no hay: ni set -euo pipefail, ni shebang ejecutable, ni una sola línea que actúe al cargarse más allá de la guarda y las constantes. veloz_morir es la única que llama a exit, y es correcto porque está pensada para usarse desde un ejecutable, no desde una sesión interactiva.
informe-diario.sh como orquestador
informe-diario.sh como orquestador#!/usr/bin/env bash
# informe-diario.sh — Informe diario de reparto de Veloz Envíos.
set -Eeuo pipefail
BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"; readonly BASE_DIR
source "$BASE_DIR/lib/comun.sh"
source "$BASE_DIR/lib/informe.sh"
: "${VELOZ_CSV:=/srv/veloz/datos/envios.csv}"
: "${VELOZ_LOG_DIR:=$BASE_DIR/logs}"
_veloz_cargar_conf "$BASE_DIR/etc/veloz-ops.conf"
main() {
local fecha subcomando; fecha=$(date +%F)
while [[ $# -gt 0 ]]; do case "$1" in # el bucle de opciones de 03-05
--fecha) veloz_validar_fecha "${2:-}" || veloz_morir 64 "Fecha inválida: ${2:-}"
fecha="$2"; shift 2 ;;
--debug) activar_debug; shift ;;
-h|--help) uso; exit 0 ;;
*) break ;;
esac; done
subcomando="${1:-resumen}"
exec 9> "$VELOZ_LOG_DIR/.informe.lock"
flock -n 9 || veloz_morir 75 "Ya hay un informe en curso"
TRABAJO=$(mktemp -d); readonly TRABAJO
trap 'rm -rf "$TRABAJO"' EXIT INT TERM
veloz_validar_entorno
acumular_dia "$fecha"
case "$subcomando" in
resumen) generar_informe "$fecha" "$VELOZ_LOG_DIR/informe-$fecha.txt" ;;
detalle) tabla_detalle "$fecha" ;;
ciudades) tabla_ciudades ;;
*) veloz_morir 64 "Subcomando desconocido: $subcomando" ;;
esac
}
main "$@"Este es el resultado de cinco módulos. El ejecutable ya no implementa casi nada: localiza su base, carga sus librerías, resuelve la configuración, procesa opciones, se protege con flock y un trap, y despacha. Toda la lógica reutilizable vive en lib/. Cuando escribas archivar-historico.sh, las tres primeras líneas serán idénticas y tendrás gratis el registro, los errores y las validaciones.
- Probar la librería a mano
Una librería bien hecha se puede cargar en una sesión interactiva, y eso es lo que la hace verificable:
$ source ~/veloz-ops/lib/comun.sh
$ veloz_porcentaje 11 128 # 8.59
$ veloz_validar_fecha 2026-02-31 && echo ok || echo malo # malo
$ declare -F | grep veloz # todas las funciones definidas
$ type veloz_porcentaje # ver el código de una funcióndeclare -F lista los nombres de las funciones definidas y type muestra el cuerpo (01-04). Si al hacer source aparece cualquier salida, hay código de nivel superior que sobra. Para pruebas automáticas de verdad —afirmaciones, casos límite, informe de resultados— la herramienta es bats, en 08-06. Y shellcheck -x sigue los source para analizar también las librerías, en 08-05.
Errores Comunes y Consejos
source lib/comun.shcon ruta relativa. Funciona en tu directorio y falla desdecrono elPATH.- Usar
$0para localizar el script. Dabashconbash script.shy el fichero equivocado dentro de una librería. Usa${BASH_SOURCE[0]}. exitdentro de una librería cargada en tu shell. Cierra tu terminal. Usareturn.- Código de nivel superior en la librería, y en especial un
set -euo pipefail: ensucia la salida e impone su política al script que la carga, quizá sin que lo espere. readonlysin guarda de inclusión, que al cargarse dos veces da error fatal, y funciones sinlocal, que machacan variables del script que las llama semanas después.- Consejo: documenta cada función con una línea
# nombre — qué hace. Uso: nombre <args>justo encima. Es lo que leerás dentro de seis meses, y lo que permite generar la ayuda del toolkit con un simplegrep '^# [a-z]' lib/*.sh.
Ejercicios
Ejercicio 1. Escribe la cabecera completa de archivar-historico.sh (en bin/) que localice BASE_DIR de forma robusta, cargue lib/comun.sh, aplique modo estricto y falle con un mensaje claro si la librería no está donde debería.
Ejercicio 2. Añade a comun.sh una función veloz_config que devuelva el valor de una clave de configuración respetando el orden de precedencia (defecto < fichero < entorno), con guarda de inclusión y sin usar eval.
Ejercicio 3. Divide el toolkit: decide para cada una de estas funciones si va en comun.sh, en informe.sh o en el propio ejecutable, y justifica: veloz_log_info, tabla_ciudades, veloz_porcentaje, acumular_dia, uso, veloz_validar_fecha.
Soluciones
Solución 1.
#!/usr/bin/env bash
# archivar-historico.sh — Archiva el histórico mensual de Veloz Envíos.
set -Eeuo pipefail
BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" || exit 78
readonly BASE_DIR LIB="$BASE_DIR/lib/comun.sh"
[[ -r "$LIB" ]] || { printf 'Falta la librería %s\n' "$LIB" >&2; exit 78; }
source "$LIB"La comprobación previa al source importa: sin ella, el mensaje sería source: lib/comun.sh: No such file or directory, que no dice dónde se buscó. El código 78 (EX_CONFIG, de la tabla de 05-03) es el adecuado: la instalación está mal, no los datos. Y el mensaje usa printf directo porque veloz_log_error aún no existe.
Solución 2.
# veloz_config — Devuelve el valor de una clave. Uso: veloz_config <CLAVE> [defecto]
veloz_config() {
local clave="${1:?}" defecto="${2:-}" valor l
valor="${!clave:-}" # 1) ¿está en el entorno o ya cargada?
if [[ -z "$valor" && -r "${VELOZ_CONF:-}" ]]; then
while IFS= read -r l; do # 2) buscar en el fichero
[[ "$l" =~ ^[[:space:]]*"$clave"=\"?([^\"]*)\"?$ ]] || continue
valor="${BASH_REMATCH[1]}"; break
done < "$VELOZ_CONF"
fi
printf '%s\n' "${valor:-$defecto}" # 3) defecto como último recurso
}${!clave} es la expansión indirecta: obtiene el valor de la variable cuyo nombre está en $clave. Es la lectura equivalente al printf -v de la sección 6, y ambas evitan eval, que ejecutaría el contenido del fichero de configuración —justo el riesgo del que huimos—. El orden de las tres ramas es la tabla de precedencia.
Solución 3.
| Función | Destino | Motivo |
|---|---|---|
veloz_log_info, veloz_porcentaje, veloz_validar_fecha |
lib/comun.sh |
Registro, aritmética y validación genéricos: no saben nada de envíos |
acumular_dia, tabla_ciudades |
lib/informe.sh |
Conocen el formato del CSV, los estados de envío y su presentación |
uso |
El ejecutable | Cada script tiene sus propias opciones; no es reutilizable |
El criterio es una sola pregunta: ¿lo necesitaría un script que no hable de envíos? Si la respuesta es sí, va a comun.sh. uso es el caso interesante: aunque todos los scripts tengan una, su contenido es distinto en cada uno, así que lo compartible no es la función sino el convenio de que exista.
Conclusión
source ejecuta un fichero en el shell actual, y de ahí sale todo lo demás: las funciones y variables que define quedan disponibles, no hace falta shebang ni permiso de ejecución, y un exit mataría a quien la carga —dentro de una librería se usa return—. Una librería contiene solo definiciones, sin código que actúe, sin set -euo pipefail que imponga políticas ajenas y sin mensajes al cargarse. Se localiza con BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)", nunca con $0 ni con rutas relativas, porque un script se invoca desde cualquier directorio y desde el PATH. Una guarda [[ -n ${_COMUN_SH:-} ]] && return 0 evita el doble cargado y el error de los readonly duplicados, y un prefijo veloz_ sustituye a los espacios de nombres que Bash no tiene, con el guion bajo inicial marcando lo privado. La configuración vive en etc/veloz-ops.conf con permisos 600 —porque cargarlo con source ejecuta lo que contenga— o se parsea como clave=valor con BASH_REMATCH y printf -v, y resuelve sus conflictos con una precedencia explícita: defecto < fichero < entorno < línea de comandos. El resultado es ~/veloz-ops/ con bin/, lib/, etc/ y logs/, y un informe-diario.sh que ya no implementa: orquesta.
Con esto se cierra el Módulo 5, y con él la parte del curso dedicada a lo que Bash sabe hacer por sí mismo. El toolkit de Veloz Envíos es hoy robusto, seguro con sus ficheros y procesos, capaz de diagnosticar sus propios fallos, preciso al validar texto, dueño de su entrada y salida, y modular. Pero sigue siendo torpe en un terreno concreto: cada vez que hay que hacer cuentas por columnas sobre el CSV, encadena cut, sort, uniq y bucles que leen el fichero varias veces; cada vez que hay que reescribir texto, faltan herramientas; y cuando la veloz-api deje de ser un proceso que se comprueba con pgrep y pase a ser una API que hay que consultar, Bash solo no llega.
En el Módulo 6 entran las herramientas externas que multiplican lo que Bash sabe hacer: awk, que procesa columnas y agrega en una sola pasada lo que hoy te cuesta veinte líneas (06-01); sed, para transformar texto en flujo con las regex que ya dominas (06-02); las órdenes para interrogar al sistema —disco, memoria, usuarios, hardware— (06-03); las herramientas de red y el /dev/tcp que dejamos apuntado (06-04); y curl con jq para hablar con la veloz-api y manejar JSON de verdad, que es donde tu toolkit dejará de leer ficheros para empezar a integrarse con el resto del sistema (06-05).
Curso de Programación en Bash
Módulo 1: Introducción a Bash
- ¿Qué es Bash?
- Configurando tu Entorno
- Navegación Básica en la Línea de Comandos
- Entendiendo el Shell
- Encontrar Ayuda: man, help y --help
Módulo 2: Comandos Básicos de Bash
- Operaciones con Archivos y Directorios
- Comandos de Procesamiento de Texto
- Permisos y Propiedad de Archivos
- Redirección y Tuberías
- Comodines y Expansión de Rutas
- Historial y Atajos de Teclado
Módulo 3: Fundamentos de Scripting
- Creando y Ejecutando un Script
- Variables y Constantes
- Operadores Básicos
- Sentencias Condicionales
- Argumentos y Entrada del Usuario
- Comillas, Expansión y Sustitución
Módulo 4: Scripting Intermedio
- Bucles en Bash
- Funciones en Bash
- Arrays y Arrays Asociativos
- Manipulación de Cadenas
- La Sentencia case y los Menús Interactivos
- Aritmética y Cálculos Numéricos
Módulo 5: Técnicas Avanzadas de Scripting
- Operaciones Avanzadas con Archivos
- Gestión de Procesos
- Manejo de Errores y Depuración
- Expresiones Regulares
- Entrada/Salida Avanzada: Descriptores y Here-Documents
- Scripts Modulares y Librerías Reutilizables
Módulo 6: Trabajando con Herramientas Externas
Módulo 7: Automatización y Programación
- Trabajos Cron
- Automatizando Tareas
- Scripts de Respaldo y Restauración
- Monitoreo y Registro
- Servicios y Temporizadores con systemd
- Automatización Remota con SSH
Módulo 8: Mejores Prácticas y Optimización
- Escribiendo Código Legible
- Optimizando Scripts en Bash
- Consideraciones de Seguridad
- Control de Versiones con Git
- Análisis Estático con ShellCheck y shfmt
- Pruebas Automatizadas con Bats
- Portabilidad: POSIX sh frente a Bashismos
