Todo el curso ha dado por supuesta una cosa: que el intérprete es Bash 5 sobre Ubuntu 24.04. Es una suposición razonable para srv-veloz-01/02/03, y por eso hemos usado arrays asociativos, mapfile, [[ ]], ${s^^} y set -o pipefail sin pensarlo dos veces. Pero el día que haya que escribir el entrypoint.sh del contenedor Alpine de la veloz-api, o un script de arranque que corre antes de que Bash esté disponible, o una herramienta que un compañero ejecutará en su macOS, esa suposición se rompe y el script falla con errores desconcertantes. Esta lección explica qué es POSIX, qué construcciones nuestras son bashismos, cuáles son sus equivalentes, por qué las herramientas externas dan más problemas que el propio shell, cómo comprobar la portabilidad y —lo más importante— cuándo merece la pena y cuándo es un lastre.
Contenido
- Qué es POSIX y qué es un bashismo
- El escenario real: quién es
/bin/shen cada sistema - Tabla de bashismos y su equivalente POSIX
- Detalles que hay que conocer al bajar a POSIX
- Las herramientas externas duelen más que el shell
- Cómo comprobar la portabilidad
- La decisión de diseño, con criterio explícito para Veloz Envíos
- Declarar la dependencia: comprobar
BASH_VERSINFO - Qué aporta cada versión de Bash y el problema de macOS
- Balance del módulo 8
- Qué es POSIX y qué es un bashismo
POSIX es un estándar (IEEE 1003.1) que define, entre muchas otras cosas, el lenguaje del shell: qué sintaxis debe entender cualquier shell que se declare conforme. Es el mínimo común denominador, y es deliberadamente austero: no tiene arrays, ni [[ ]], ni sustitución de cadenas en las expansiones.
Un bashismo es cualquier construcción que Bash entiende y el estándar no exige. No son errores —son las razones por las que Bash es cómodo—, pero solo funcionan donde hay Bash. El problema surge por una confusión concreta: escribir #!/bin/sh en la primera línea y usar sintaxis de Bash dentro. En Ubuntu, si además lo pruebas ejecutando bash script.sh, funciona; en el contenedor Alpine, donde /bin/sh es otro programa, falla.
- El escenario real: quién es
/bin/sh en cada sistema
/bin/sh en cada sistema| Sistema | /bin/sh es |
Consecuencia |
|---|---|---|
| Debian / Ubuntu | dash |
Rápido y estricto: ningún bashismo funciona |
| Alpine (contenedores) | ash (BusyBox) |
Aún más reducido; además las herramientas son BusyBox |
| RHEL / Fedora | bash en modo POSIX |
Muchos bashismos «funcionan»: falsa sensación de seguridad |
| macOS | bash 3.2 o zsh |
Bash antiguo: sin arrays asociativos ni mapfile |
| FreeBSD / Solaris | sh propio, ksh |
Diferencias en los detalles |
Las dos filas peligrosas son la de RHEL y la de macOS, por motivos opuestos. En RHEL tu script con bashismos y #!/bin/sh funciona, así que nadie detecta el problema hasta que llega a Debian. En macOS el intérprete se llama bash pero es la versión 3.2 de 2006 —Apple no actualiza por motivos de licencia—, así que un script perfectamente correcto para Bash 5 falla con mensajes crípticos.
El error típico se ve así:
[[: not found es la firma inconfundible de un bashismo en dash: no es un error de sintaxis, es que dash busca una orden llamada [[.
- Tabla de bashismos y su equivalente POSIX
| Bashismo | Equivalente POSIX | Nota |
|---|---|---|
[[ $a == $b ]] |
[ "$a" = "$b" ] |
En POSIX las comillas son obligatorias |
[[ $s == a* ]] |
case $s in a*) ;; esac |
POSIX no tiene comparación con patrón en [ ] |
[[ $s =~ regex ]] |
expr "$s" : 'regex' o grep -q |
Sin BASH_REMATCH; el más doloroso |
arr=(a b c), ${arr[@]} |
Parámetros posicionales: set -- a b c, "$@" |
Un solo «array» por ámbito |
declare -A |
No existe | Sin arrays asociativos: fichero temporal o awk |
${s//a/b} |
printf '%s' "$s" | sed 's/a/b/g' |
Cuesta un proceso |
${s^^} / ${s,,} |
tr '[:lower:]' '[:upper:]' |
Idem |
${s:0:3} |
printf '%s' "$s" | cut -c1-3 |
${s#...} y ${s%...} sí son POSIX |
$(( a + b )) |
$(( a + b )) |
Sí es POSIX, úsalo sin miedo |
(( i++ )) |
i=$(( i + 1 )) |
La orden (( )) no es POSIX |
local x |
(no está en el estándar) | Lo implementan dash, ash y ksh: seguro en la práctica |
function f { }, source f |
f() { }, . f |
La forma con paréntesis y el punto son las portables |
echo -e "a\tb" |
printf 'a\tb\n' |
echo con opciones no es portable |
<<< "$cadena" |
<<EOF … EOF o printf ... | |
Here-string es de Bash |
<(cmd) |
Fichero temporal con mktemp |
Sustitución de proceso: sin equivalente |
arr+=(x), s+=texto |
s="${s}texto" |
El operador += no es POSIX |
${BASH_SOURCE[0]}, read -a, mapfile |
$0; while read + set -- |
Sin arrays no hay equivalente directo |
set -o pipefail, trap ... ERR |
No existen | Ver apartado 4; solo trap ... EXIT INT TERM |
{1..10} |
seq 1 10 o while |
La expansión de llaves no es POSIX |
&> fichero |
> fichero 2>&1 |
Muy fácil de olvidar |
- Detalles que hay que conocer al bajar a POSIX
[ ] no es [[ ]]. Es una orden, no sintaxis, así que sus argumentos sufren división de palabras. [ $a = $b ] con $a vacía se convierte en [ = valor ] y da un error de sintaxis. Con comillas —[ "$a" = "$b" ]— funciona siempre. Usa =, no ==, y -a/-o están obsoletos: encadena con && y || entre dos [ ].
Sin pipefail. Es la pérdida más seria, porque cmd1 | cmd2 devuelve el código de cmd2 y un fallo del primero pasa inadvertido (05-03). La salida practicable es romper la tubería: escribir la primera etapa en un temporal con mktemp, comprobar su código de salida y leer el temporal en la segunda. En la práctica, un script POSIX se reestructura para no encadenar órdenes cuyo fallo importe.
Sin arrays. El sustituto son los parámetros posicionales:
set -- Valencia Sevilla Bilbao Madrid # "array" de 4 elementos
for ciudad in "$@"; do printf 'procesando %s\n' "$ciudad"; done
printf 'total: %d\n' "$#"Es un único conjunto por ámbito, así que si necesitas dos listas a la vez, o guardas una en una función o pasas a awk.
Sin local, en teoría. El estándar no lo incluye, pero dash, ash, ksh y zsh lo implementan. La recomendación práctica: úsalo, y documenta en la cabecera que el script requiere un shell con local.
- Las herramientas externas duelen más que el shell
Esta es la parte que casi todo el mundo subestima. Puedes escribir un script POSIX impecable y que falle en macOS porque sed -i funciona distinto. Linux usa GNU coreutils; macOS y los BSD usan las herramientas BSD; Alpine usa BusyBox, que es una tercera implementación reducida.
| Uso | GNU (Linux) | BSD / macOS | Portable |
|---|---|---|---|
| Editar en sitio | sed -i 's/a/b/' f |
sed -i '' 's/a/b/' f |
sed 's/a/b/' f > t && mv t f |
| Fecha relativa | date -d '3 days ago' |
date -v-3d |
Calcular con $(( )) sobre date +%s |
| Fecha desde época | date -d "@$s" |
date -r "$s" |
Ninguna: detectar la variante |
| Ruta absoluta | readlink -f |
No existe (usa realpath o stat) |
cd "$(dirname "$f")" && pwd |
| Regex Perl | grep -P '\d+' |
No soportado | grep -E '[0-9]+' |
| Tamaño de fichero | stat -c %s f |
stat -f %z f |
wc -c < f |
| Sin entrada, no ejecutar | xargs -r |
Comportamiento por defecto | xargs -r solo en GNU/BusyBox |
| Formato en find | find . -printf '%s\n' |
No existe | find . -exec stat ... \; |
Tres estrategias, en orden de preferencia:
- Ceñirse a las opciones POSIX de cada herramienta.
sed 's/a/b/'sin-i,grep -E,find -exec. Es lo más robusto y casi siempre suficiente. - Detectar la variante al arrancar y adaptarse:
if date -d '@0' >/dev/null 2>&1
then fecha_de_epoca() { date -d "@$1" +%F; } # GNU
else fecha_de_epoca() { date -r "$1" +%F; } # BSD
fi- Declarar la dependencia: exigir
coreutilsy comprobarlo conveloz_requiere(06-03). En macOS se instalan conbrew install coreutils, aunque quedan con prefijog(gsed,gdate).
- Cómo comprobar la portabilidad
Tres herramientas complementarias, más una prueba real:
$ checkbashisms entrypoint.sh # paquete devscripts
possible bashism in entrypoint.sh line 14 (should be '.', not 'source'):
source /etc/veloz/env.sh
possible bashism in entrypoint.sh line 22 ([[ ... ]]):
if [[ -z "$VELOZ_API_URL" ]]; then
$ shellcheck -s sh entrypoint.sh # 08-05, dialecto POSIX
In entrypoint.sh line 22:
^-- SC2039 (warning): In POSIX sh, [[ ]] is undefined.
$ dash -n entrypoint.sh # solo sintaxis, pero es el interprete real
$ dash entrypoint.sh # ejecucion real: lo mas fiablecheckbashisms es el especialista y detecta cosas que ShellCheck no marca; shellcheck -s sh se integra con el resto del flujo y explica el porqué; dash -n valida la sintaxis con el intérprete que realmente se usará en Debian. Pero la comprobación definitiva es ejecutarlo donde va a correr:
Ahí se descubren a la vez los bashismos y las diferencias de BusyBox, que son la mitad del problema. Merece un trabajo de CI (08-05) junto al de ShellCheck.
- La decisión de diseño, con criterio explícito para Veloz Envíos
POSIX puro no es «mejor»: es una restricción que se paga en legibilidad y en procesos. Un ${s^^} se convierte en un tr con su fork, y un array asociativo que resolvía una agregación en diez líneas se convierte en un fichero temporal ordenado. Escribir en POSIX lo que va a correr siempre en Ubuntu es pagar un coste sin comprar nada.
| Merece la pena POSIX | Es un lastre |
|---|---|
entrypoint.sh de un contenedor mínimo sin Bash |
Herramientas internas de un parque homogéneo |
Scripts de arranque y initramfs |
Scripts con lógica de datos y agregación |
| Instaladores que se ejecutan en sistemas desconocidos | Automatización con arrays y expresiones regulares |
Fragmentos que otros incrustan en su /bin/sh |
Todo lo que ya funciona en Bash 5 |
Criterio para el toolkit, escrito y aplicado:
lib/comun.shy los cinco scripts debin/siguen siendo Bash 5, con#!/usr/bin/env bash, arrays asociativos,[[ ]],mapfileyset -euo pipefail. Corren en tres servidores idénticos que administramos nosotros; renunciar a esas herramientas empeoraría el código sin ganancia alguna.- El
entrypoint.shdel contenedor de laveloz-apise escribe en POSIX puro con#!/bin/sh. La imagen base es Alpine, no lleva Bash, y añadirlo solo para el arranque significa aumentar la imagen y su superficie de ataque. El script es corto —comprueba variables de entorno, espera a la base de datos y haceexecdel servicio—, justo el tipo de lógica que POSIX cubre sin esfuerzo.
#!/bin/sh
# entrypoint.sh - arranque de veloz-api. POSIX puro: la imagen Alpine no lleva bash.
set -eu # sin pipefail: no es POSIX
: "${VELOZ_API_PUERTO:=8080}" # valor por defecto, POSIX
: "${VELOZ_BD_HOST:?falta VELOZ_BD_HOST}" # obligatoria o abortar (03-06)
espera=0
while ! nc -z "$VELOZ_BD_HOST" 5432; do
espera=$(( espera + 1 ))
[ "$espera" -ge 30 ] && { echo "la base de datos no responde" >&2; exit 1; }
sleep 1
done
echo "veloz-api arrancando en el puerto $VELOZ_API_PUERTO"
exec /usr/local/bin/veloz-api --port "$VELOZ_API_PUERTO" "$@"Ni un bashismo: [ ] en lugar de [[ ]], $(( )) que sí es POSIX, : con ${var:=} y ${var:?}, echo sin opciones y exec final para que el servicio herede el PID 1 y reciba las señales (05-02). La decisión queda documentada en la cabecera, que es tan importante como la decisión misma.
- Declarar la dependencia: comprobar
BASH_VERSINFO
BASH_VERSINFOSi un script requiere Bash 4 o superior, dilo y falla pronto con un mensaje claro, en lugar de reventar cincuenta líneas después con «syntax error near unexpected token». La comprobación va al principio de lib/comun.sh, justo después del shebang, y usa BASH_VERSINFO (01-02):
#!/usr/bin/env bash
# lib/comun.sh - libreria comun del toolkit. Requiere Bash 4.3+.
if [ -z "${BASH_VERSINFO:-}" ]; then
echo "Error: este script requiere Bash, no sh/dash." >&2
exit 1
fi
if (( BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 3) )); then
echo "Error: se requiere Bash 4.3 o superior; encontrado ${BASH_VERSION}." >&2
echo "En macOS: brew install bash y use /opt/homebrew/bin/bash." >&2
exit 1
fiDos detalles del código. La primera comprobación se escribe con [ ] y echo a propósito: si el intérprete resulta ser dash, esas dos líneas tienen que funcionar para poder dar el mensaje; un [[ ]] aquí produciría el error críptico que precisamente queremos evitar. Y el mensaje dice qué hacer, no solo qué falta: un error que indica la solución ahorra media hora a quien lo encuentre.
- Qué aporta cada versión de Bash y el problema de macOS
| Versión | Novedades relevantes | Año |
|---|---|---|
| 3.2 | La de macOS. Sin arrays asociativos, sin mapfile, sin ${s^^}, sin &>> |
2006 |
| 4.0 | Arrays asociativos (declare -A), mapfile/readarray, ${s^^}/${s,,}, |& |
2009 |
| 4.2 | printf -v con %(fmt)T (fechas sin date), declare -g |
2011 |
| 4.3 | declare -n (referencias de nombre, 08-03), mejoras en [[ ]] |
2014 |
| 4.4 | ${var@Q} (entrecomillado seguro), mapfile -d, local - |
2016 |
| 5.0+ | EPOCHSECONDS y EPOCHREALTIME (tiempo sin date), SRANDOM |
2018-22 |
La frontera práctica es Bash 4.0: casi todo lo que hace cómodo a Bash moderno llegó ahí. Ubuntu 24.04 trae 5.2, así que en la flota no hay problema.
El problema es macOS, que sigue con 3.2 de 2006 por la licencia GPLv3 de las versiones posteriores. Si un compañero desarrolla en Mac, sus scripts fallarán al usar declare -A o mapfile aunque en los servidores funcionen. Tres respuestas posibles: instalar Bash moderno con Homebrew y usar #!/usr/bin/env bash con el nuevo primero en el PATH —la opción normal—; ceñirse a Bash 3.2, que es renunciar a demasiado; o desarrollar dentro de un contenedor con la misma imagen que producción, que además elimina el resto de diferencias de herramientas del apartado 5.
- Balance del módulo 8
El módulo empezó con tres preguntas sobre el toolkit: ¿se entiende?, ¿es rápido?, ¿es seguro? Siete lecciones después, ~/veloz-ops/ es otra cosa:
| Lección | Qué aportó |
|---|---|
| 08-01 | Legible: estilo uniforme, estructura canónica, funciones cortas, comentarios que explican el porqué |
| 08-02 | Rápido: medido antes y después; informe-diario.sh de 3 min 12 s a 3,9 s |
| 08-03 | Auditado: sin eval, entrada validada con lista blanca, secretos fuera del código, temporales seguros, sudo acotado |
| 08-04 | Versionado: historia con motivos, ramas, etiquetas, .gitignore que protege la configuración, hook pre-commit |
| 08-05 | Analizado: 47 avisos corregidos, entre ellos un rm -rf con variable vacía; formato automático con shfmt |
| 08-06 | Probado: tests/ con dobles y datos ficticios, ejecutado en cada commit y en CI |
| 08-07 | Con su portabilidad decidida: Bash 5 documentado y comprobado, POSIX donde de verdad hace falta |
Y todo sin añadir ni una función nueva. Esa es la idea del módulo: la diferencia entre un script que funciona y software del que fiarse no está en lo que hace, sino en cómo está hecho.
Errores Comunes y Consejos
- Poner
#!/bin/shy escribir Bash. El error central. Si usas bashismos, el shebang es#!/usr/bin/env bash. - Probar con
bash script.shun script que declarash. El shebang se ignora al invocar así, y el problema queda oculto. Prueba condash script.sh. - Creer que POSIX es «más seguro» o «mejor». Es más restrictivo: elegirlo sin necesidad produce código más largo, más lento y más difícil de leer.
- Olvidar las comillas en
[ ]. En[[ ]]perdonaba; aquí una variable vacía produce un error de sintaxis. - Suponer que las herramientas son GNU.
sed -i,date -dyreadlink -fno existen igual en macOS ni en BusyBox, y ahí no ayuda ningún shell. - Consejo: un contenedor Alpine en CI cuesta segundos y detecta a la vez los bashismos y las diferencias de BusyBox.
- Consejo: escribe la decisión en la cabecera del fichero. «Requiere Bash 4.3+» o «POSIX puro: la imagen no lleva bash» evita que el siguiente la deshaga sin querer.
Ejercicios
Ejercicio 1. Convierte este fragmento a POSIX puro manteniendo el comportamiento.
#!/usr/bin/env bash
ciudades=(Valencia Sevilla Bilbao)
for c in "${ciudades[@]}"; do
[[ $c == V* ]] && echo -e "prioritaria:\t${c^^}"
doneEjercicio 2. Escribe una función veloz_hace_dias que devuelva la fecha de hace N días en formato AAAA-MM-DD y funcione tanto con date de GNU como de BSD.
Ejercicio 3. Un compañero informa de que informe-diario.sh falla en su macOS con declare: -A: invalid option. Explica la causa y propón dos soluciones con sus contrapartidas.
Soluciones
Solución 1.
#!/bin/sh
set -- Valencia Sevilla Bilbao # parametros posicionales como "array"
for c in "$@"; do
case $c in
V*) mayus=$(printf '%s' "$c" | tr '[:lower:]' '[:upper:]')
printf 'prioritaria:\t%s\n' "$mayus" ;;
esac
doneCuatro sustituciones: el array pasa a set -- y "$@"; la comparación con patrón [[ $c == V* ]] pasa a case, que es la forma POSIX de comparar con patrones; ${c^^} pasa a tr; y echo -e pasa a printf, que interpreta \t de forma portable y además evita el problema de que echo -e imprima literalmente -e en algunos shells.
Solución 2.
# veloz_hace_dias <n> -> fecha AAAA-MM-DD de hace n dias
veloz_hace_dias() {
if date -d '@0' +%F >/dev/null 2>&1; then
date -d "$1 days ago" +%F # GNU
else
date -v "-$1"d +%F # BSD / macOS
fi
}La detección se hace probando una opción exclusiva de GNU y descartando su salida y su error. La alternativa realmente universal es aritmética sobre la época —date -d "@$(( $(date +%s) - 86400 * $1 ))"— pero tiene el mismo problema al convertir de vuelta, y además ignora los cambios de horario, así que la detección de variante es preferible.
Solución 3. La causa es que macOS trae Bash 3.2, anterior a los arrays asociativos que llegaron en 4.0, y #!/usr/bin/env bash resuelve al /bin/bash del sistema. Dos soluciones:
- Instalar Bash moderno con
brew install bashy anteponer su directorio alPATH, de modo queenv bashencuentre el 5.x. Contrapartida: cada persona debe configurar su máquina, y unPATHmal ordenado reproduce el fallo en silencio. - Desarrollar en un contenedor con la misma imagen que producción. Contrapartida: flujo de trabajo algo más pesado; ventaja: elimina también las diferencias de
sed,dateystatdel apartado 5, que aparecerían igualmente.
Reescribir el toolkit para Bash 3.2 sería una tercera opción, y es la peor: penaliza a tres servidores para acomodar a un portátil. En cualquier caso, la comprobación de BASH_VERSINFO del apartado 8 habría convertido ese error críptico en un mensaje que explica qué hacer.
Conclusión
La portabilidad es una decisión, no una virtud automática. POSIX define el mínimo común denominador del shell, y todo lo que Bash añade por encima —[[ ]], arrays, ${s//a/b}, ${s^^}, mapfile, <<<, <( ), +=, set -o pipefail, trap ERR— es un bashismo que desaparece en cuanto /bin/sh es dash en Debian, ash en Alpine o ksh en otro Unix; el error [[: not found es su firma. La tabla de equivalencias cubre lo esencial: [ "$a" = "$b" ] con comillas obligatorias, case para comparar con patrones, set -- y "$@" como sustituto de los arrays, tr y sed en lugar de las expansiones de cadena, . en vez de source, printf en vez de echo -e y reestructurar el código allí donde falta pipefail. Pero el problema mayor no está en el shell sino en las herramientas externas: sed -i, date -d, readlink -f, grep -P y stat se comportan de forma distinta en GNU, BSD y BusyBox, y ante eso solo caben tres estrategias —ceñirse a las opciones POSIX, detectar la variante al arrancar o declarar la dependencia de coreutils—. La portabilidad se comprueba, no se supone: checkbashisms, shellcheck -s sh, dash -n y, sobre todo, una ejecución real en un contenedor Alpine dentro de CI. El criterio de Veloz Envíos queda escrito: lib/comun.sh y los cinco scripts de bin/ siguen siendo Bash 5 porque corren en tres servidores idénticos y renunciar a sus herramientas no compraría nada, mientras que el entrypoint.sh del contenedor de la veloz-api se escribe en POSIX puro porque la imagen Alpine no lleva Bash y su lógica es corta. Esa dependencia se declara y se comprueba con BASH_VERSINFO al arrancar, en [ ] y echo para que el mensaje llegue incluso si el intérprete es dash, y diciendo qué hacer y no solo qué falta. La frontera práctica es Bash 4.0 —arrays asociativos, mapfile, ${s^^}— con declare -n en 4.3, y el obstáculo recurrente es el Bash 3.2 de macOS, que se resuelve con Homebrew o desarrollando en contenedor.
Con esto se cierra el módulo 8, y con él la transformación del toolkit: los mismos cinco scripts y la misma librería que empezaron el módulo son ahora legibles, rápidos, auditados, versionados, analizados, probados y con su portabilidad decidida, sin haber añadido una sola función nueva. Ya sabes escribir Bash y ya sabes escribirlo bien. Lo que queda es juntarlo todo. El Módulo 9 son cinco proyectos completos construidos paso a paso, aplicando a la vez todo lo aprendido: un recolector de información del sistema (09-01), un analizador de logs que agrega, detecta patrones y genera informes (09-02), un sistema de respaldo automatizado con retención, verificación y restauración probada (09-03), un monitor de red con umbrales y alertas (09-04) y, como cierre del curso, la integración final del toolkit (09-05), donde las piezas de los cuatro proyectos anteriores se unifican en una herramienta única, instalable, versionada, probada y desplegada en la flota de Veloz Envíos.
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
