informe-diario.sh ya guarda sus rutas en constantes y sus recuentos en variables, pero sigue siendo ciego: tiene un UMBRAL_ERRORES=50 que nadie compara con nada, y da por hecho que envios.csv existe. Para que el script empiece a decidir necesita antes un vocabulario: los operadores. En esta lección aprenderás a encadenar comandos según su resultado, a comprobar si un fichero existe, a comparar números y cadenas —y a no confundir ambas cosas, que es el error más frecuente del scripting en Bash—. Todavía no escribiremos ningún if: eso es la lección siguiente. Aquí construimos las piezas que irán dentro.
Contenido
- Panorama: cuatro familias de operadores
- Operadores de control de flujo:
;,&&,||,! - Agrupación:
{ ...; }frente a( ... ) - El comando
testy sus tres formas - Comparación numérica frente a comparación de cadenas
- Cadenas: igualdad, orden y vacío
- Operadores de fichero
- Operadores lógicos dentro de
[[ ]] - Aritmética como operador:
(( ))y$(( )) - Precedencia y paréntesis
- Panorama: cuatro familias de operadores
En Bash conviven varios sistemas de operadores que se parecen pero no son intercambiables. Distinguirlos desde el principio te ahorrará horas de desconcierto:
| Familia | Símbolos | Dónde se escriben | Qué operan |
|---|---|---|---|
| Control de flujo | ; && || ! & |
Entre comandos | Códigos de salida |
| Comparación (test) | -eq -lt = -f -z |
Dentro de [ ] o [[ ]] |
Cadenas, números y ficheros |
| Aritméticos | + - * / % > < |
Dentro de (( )) o $(( )) |
Solo enteros |
| Expansión | ${var:-x} $(cmd) |
En cualquier palabra | Texto |
Los tres primeros grupos son el contenido de esta lección; la familia de expansión es la de 03-06. La confusión típica está entre las filas 2 y 3: > dentro de [[ ]] compara texto alfabéticamente, mientras que > dentro de (( )) compara números. Mismo símbolo, semántica distinta según el contexto.
- Operadores de control de flujo:
;, &&, ||, !
;, &&, ||, !Todo comando en Linux devuelve un código de salida: 0 si tuvo éxito, distinto de 0 si falló (lección 03-01). Los operadores de control de flujo encadenan comandos usando ese código como criterio.
| Operador | Nombre | Comportamiento |
|---|---|---|
; |
Secuencia | Ejecuta el segundo siempre, pase lo que pase con el primero |
&& |
Y lógico | Ejecuta el segundo solo si el primero tuvo éxito (código 0) |
|| |
O lógico | Ejecuta el segundo solo si el primero falló (código ≠ 0) |
! |
Negación | Invierte el código de salida del comando |
& |
Segundo plano | Lanza el comando sin esperarlo (02-06) |
mkdir -p ~/veloz-ops/logs ; echo "seguimos pase lo que pase"
mkdir -p ~/veloz-ops/logs && echo "directorio listo" # solo si el mkdir fue bien
grep -q ERROR /var/log/veloz/app.log || echo "hoy no hay errores"Se llaman de evaluación perezosa (o cortocircuito) porque Bash solo ejecuta el segundo comando si hace falta para conocer el resultado: con &&, si el primero falla ya sabe que el conjunto falla; con ||, si el primero funciona ya sabe que el conjunto funciona.
El idioma más valioso del apartado es comando_crítico || exit 1, que se lee «haz esto o, si falla, aborta». Es la forma más compacta de escribir una validación:
cd /srv/veloz/datos || exit 1 # si el cd falla, no sigas trabajando a ciegas
grep -q ERROR "$LOG" && echo "hay errores" || echo "todo limpio"
! grep -q ERROR "$LOG" && echo "el log está limpio" # ! invierte el resultadoLa primera línea es importante: un cd que falla y un script que continúa es una receta para desastres, porque los comandos siguientes se ejecutarán en el directorio equivocado.
Cuidado con el patrón de la segunda línea: no es un if/else de verdad. Si echo "hay errores" fallase (por ejemplo, con el disco lleno), se ejecutaría también la rama del ||. Sirve para casos triviales; para lógica real usa if, que es 03-04.
- Agrupación:
{ ...; } frente a ( ... )
{ ...; } frente a ( ... )Cuando el && o el || deben afectar a varios comandos, hay que agruparlos. Bash ofrece dos formas con una diferencia crucial:
| Forma | Dónde se ejecuta | Sintaxis |
|---|---|---|
{ cmd1; cmd2; } |
En el shell actual | Necesita espacios tras { y ; (o salto) antes de } |
( cmd1; cmd2 ) |
En un subshell (proceso hijo) | Sin requisitos especiales |
Esa línea es el patrón de validación que más veces escribirás: si el fichero no existe, imprime un error en stderr (>&2, lección 02-04) y sale con el código 3. Las llaves son imprescindibles aquí: sin ellas, el exit 3 se ejecutaría siempre.
Los espacios de las llaves no son opcionales: {echo hola;} da error de sintaxis porque Bash lee {echo como un solo nombre de comando, y hace falta el ; (o un salto de línea) antes de la llave de cierre.
Los paréntesis crean un subshell, con todo lo que eso implica según 01-04: los cambios de directorio y las variables definidas dentro no salen. Por eso ( cd /srv/veloz/datos && wc -l envios.csv ) no altera tu directorio actual. Regla práctica: llaves por defecto (no cuestan un proceso) y paréntesis solo cuando quieras aislamiento deliberado.
- El comando
test y sus tres formas
test y sus tres formasAquí llega la pieza central. Para preguntar «¿existe este fichero?» o «¿es este número mayor que aquel?», Bash usa test. Y test es un comando, no una sintaxis especial: recibe argumentos y devuelve 0 (verdadero) o 1 (falso).
Existen tres formas de invocarlo, históricamente distintas: test -f "$csv" (forma original), [ -f "$csv" ] (sinónimo POSIX) y [[ -f "$csv" ]] (palabra clave de Bash).
Que [ sea un comando y no un símbolo explica el error más desconcertante del principiante: [-f fichero] falla con orden no encontrada, porque Bash busca un programa llamado [-f. Los espacios interiores son obligatorios, y el ] es literalmente el último argumento que [ exige recibir.
| Aspecto | [ ] (comando test) |
[[ ]] (palabra clave de Bash) |
|---|---|---|
| Portabilidad | POSIX: funciona en sh, dash, todos |
Solo Bash, Zsh, Ksh |
| Variables sin comillas | Peligroso: si está vacía, error de sintaxis | Seguro: no hay división en palabras |
| Globbing del valor | Se expande, con sorpresas | No se expande |
&& y || internos |
No los admite; hay que usar -a / -o |
Los admite con precedencia correcta |
| Comparación de patrones | No | Sí: [[ $ciudad == Val* ]] |
| Expresiones regulares | No | Sí: [[ $l =~ ^ERROR ]] (05-04) |
Comparación numérica con < |
No (compara texto) | Tampoco: usa -lt o (( )) |
La diferencia práctica más importante es la segunda fila. Con ciudad="", la expresión [ $ciudad = "Valencia" ] produce bash: [: =: se esperaba una expresión unaria, mientras que [[ $ciudad = "Valencia" ]] funciona y simplemente resulta falsa.
Con [ ], la variable vacía desaparece antes de que test la vea, y este recibe [ = Valencia ], que no tiene sentido. Con [[ ]], Bash no expande palabras dentro, así que no hay problema. La solución con [ ] es entrecomillar siempre ([ "$ciudad" = "Valencia" ]), pero es una precaución que hay que recordar cada vez.
Recomendación de este curso: usa [[ ]] siempre que escribas para Bash. Solo recurre a [ ] cuando el script deba funcionar con sh puro (08-07).
- Comparación numérica frente a comparación de cadenas
Este es el apartado que más errores previene. Como en Bash todo es texto (03-02), el shell no puede adivinar si quieres comparar números o palabras: se lo tienes que decir con el operador.
| Comparación | Numérica | De cadenas |
|---|---|---|
| Igual / distinto | -eq / -ne |
= o == / != |
| Menor / menor o igual | -lt / -le |
< / (no existe) |
| Mayor / mayor o igual | -gt / -ge |
> / (no existe) |
Los nombres vienen del inglés y son fáciles de recordar: equal, not equal, less than, less or equal, greater than, greater or equal.
Y ahora el error clásico, que conviene ver con números concretos:
[[ $total_errores -gt $UMBRAL_ERRORES ]] # ¿más errores de los tolerables?
[[ 100 -gt 9 ]] ; echo $? # 0 → verdadero: 100 es mayor que 9
[[ "100" > "9" ]] ; echo $? # 1 → FALSO: "100" va antes que "9" alfabéticamenteLa última comparación es alfabética: carácter a carácter, 1 viene antes que 9, así que "100" es «menor» que "9". Es exactamente el mismo criterio con el que un diccionario coloca «casa» antes que «zapato». Si comparas números con > o <, tu script funcionará por casualidad con cifras de la misma longitud y fallará el día que los errores pasen de 9 a 100.
Regla: números con -eq -ne -lt -le -gt -ge; texto con = == != < >. Sin excepciones. Dos avisos finales. Dentro de [ ], los símbolos < y > deben escribirse escapados (\<, \>) porque si no Bash los interpreta como redirecciones y crea un fichero llamado 9. Dentro de [[ ]] no hace falta. Y los operadores numéricos exigen que ambos lados sean enteros: si la variable está vacía o contiene texto, obtendrás se esperaba una expresión entera.
- Cadenas: igualdad, orden y vacío
Además de comparar, a menudo necesitas saber si una variable tiene contenido:
| Operador | Verdadero si… |
|---|---|
-z "$var" / -n "$var" |
La cadena tiene longitud cero / no está vacía |
$a = $b / $a != $b |
Son idénticas (en [[ ]], == es sinónimo) / son distintas |
$a < $b |
$a va antes alfabéticamente (según la configuración regional) |
[[ -n $ciudad ]] || { echo "Falta indicar la ciudad" >&2; exit 2; }
[[ $ciudad == Val* ]] && echo "empieza por Val"
[[ ${ciudad,,} == "valencia" ]] && echo "coincide sin distinguir mayúsculas"La segunda línea muestra algo que solo [[ ]] permite: el lado derecho sin comillas se trata como un patrón de globbing (02-05), así que Val* casa con cualquier ciudad que empiece por «Val». Si lo entrecomillas ("Val*"), pasa a ser texto literal y no casará.
Un detalle sobre el orden alfabético: depende de LC_COLLATE. En español, [[ "año" < "banco" ]] puede dar un resultado distinto que en la configuración C. Para comparaciones deterministas, la práctica habitual es fijar LC_ALL=C.
- Operadores de fichero
Esta es la familia que convierte a informe-diario.sh en un script robusto: comprobar antes de trabajar.
| Operador | Verdadero si la ruta… |
|---|---|
-e ruta |
Existe (sea lo que sea) |
-f ruta |
Existe y es un fichero regular |
-d ruta |
Existe y es un directorio |
-s ruta |
Existe y no está vacía (tamaño > 0) |
-r ruta |
Es legible por el usuario actual |
-w ruta |
Es escribible |
-x ruta |
Es ejecutable (o directorio atravesable) |
-L ruta |
Es un enlace simbólico (05-01) |
f1 -nt f2 / f1 -ot f2 |
f1 es más reciente / más antiguo que f2 |
[[ -r "$RUTA_APP_LOG" ]] || echo "sin permiso de lectura sobre el log" >&2
[[ -s "$RUTA_CSV" ]] || echo "atención: el CSV está vacío" >&2
[[ -d "$DIR_INFORMES" ]] || mkdir -p "$DIR_INFORMES" # "asegura que existe"La distinción entre -e, -f y -s importa más de lo que parece. -e solo dice que la ruta existe: un directorio llamado envios.csv también la satisface. -f garantiza que es un fichero de verdad. Y -s añade que tiene contenido, que es lo que quieres saber realmente: un CSV vacío existe, es legible y produce un informe con todo a cero sin dar ningún error. Ese es el fallo silencioso que -s previene.
-nt y -ot tienen un uso muy concreto en operaciones: saber si un informe está al día respecto a sus datos de origen, con [[ "$RUTA_CSV" -nt "$informe" ]].
- Operadores lógicos dentro de
[[ ]]
[[ ]]Para combinar condiciones, [[ ]] admite && y || dentro de los corchetes:
[[ -f "$RUTA_CSV" && -r "$RUTA_CSV" ]] && echo "existe y puedo leerlo"
[[ -z $ciudad || $ciudad == "todas" ]] && echo "analizar todas las ciudades"El comando [ ] no los admite; usa -a (and) y -o (or), como en [ -f "$RUTA_CSV" -a -r "$RUTA_CSV" ]. Evita -a y -o. Están marcados como obsoletos en el propio estándar POSIX porque su análisis sintáctico es ambiguo: test recibe una lista plana de argumentos y tiene que adivinar dónde empieza y acaba cada expresión, lo que produce resultados incorrectos con ciertos valores. Además no hacen cortocircuito: [ -f "$f" -a -r "$f" ] evalúa las dos partes siempre.
Hay otra diferencia práctica notable: dentro de [[ ]], && sí cortocircuita, lo que permite escribir comprobaciones dependientes con seguridad. En [[ -f "$f" && $(wc -l < "$f") -gt 0 ]], si el fichero no existe el wc no llega a ejecutarse y no aparece ningún mensaje de error espurio. Con -a no tendrías esa garantía.
- Aritmética como operador:
(( )) y $(( ))
(( )) y $(( ))Bash ofrece un contexto aritmético donde los operadores recuperan su significado matemático habitual. Hay dos variantes:
| Sintaxis | Devuelve | Se usa para |
|---|---|---|
(( expr )) |
Un código de salida | Comparar y evaluar como condición |
$(( expr )) |
Un valor | Calcular y sustituir el resultado |
total_errores=37; UMBRAL_ERRORES=50
(( total_errores > UMBRAL_ERRORES )) && echo "AVISO: demasiados errores"
porcentaje=$(( total_errores * 100 / UMBRAL_ERRORES ))
echo "Estás al ${porcentaje}% del umbral" # → Estás al 74% del umbralDos ventajas notables. Primera: dentro de (( )) el $ es opcional en los nombres de variable, porque todo lo que no sea un número se interpreta como tal. Segunda: puedes usar <, >, <=, >=, == y != con su significado numérico natural, lo que se lee mucho mejor que -lt y compañía.
Hay un detalle que sorprende y produce fallos silenciosos: (( )) devuelve 1 (fallo) cuando el resultado de la expresión es 0, siguiendo la convención de C, invertida respecto a los códigos de salida del shell.
(( 0 )) ; echo $? # 1 → "falso"
(( 5 )) ; echo $? # 0 → "verdadero"
contador=0; (( contador++ )) ; echo $? # 1, aunque la operación funcionóLa última línea es la trampa real: si tienes set -e activo (05-03), un (( contador++ )) con el contador a cero abortará el script. La solución idiomática es (( contador++ )) || true.
El repertorio aritmético completo —%, **, incrementos, bases numéricas, decimales con bc— es la lección 04-06. Aquí nos interesa (( )) solo como operador de comparación.
- Precedencia y paréntesis
Cuando mezclas operadores, Bash aplica una precedencia establecida: && se evalúa antes que || dentro de [[ ]], igual que en la mayoría de lenguajes. Pero entre comandos, && y || tienen la misma precedencia y se evalúan de izquierda a derecha, lo cual es una fuente clásica de sorpresas.
[[ -f "$RUTA_CSV" || -f "$RUTA_ALT" ]] && [[ -r "$RUTA_CSV" ]] # explícito
[[ ( -f "$f" || -d "$f" ) && -r "$f" ]] # paréntesis internosEl consejo profesional es sencillo: no confíes en la precedencia, escribe paréntesis. Dentro de [[ ]] se escriben tal cual, con espacios alrededor; dentro de [ ] hay que escaparlos (\\( y \\)), otra razón más para preferir [[ ]].
Con todo esto, las validaciones que informe-diario.sh necesita ya se pueden expresar por completo:
[[ -f "$RUTA_CSV" && -s "$RUTA_CSV" ]] || { echo "CSV ausente o vacío" >&2; exit 3; }
[[ -r "$RUTA_APP_LOG" ]] || { echo "No puedo leer el log" >&2; exit 4; }
(( total_errores > UMBRAL_ERRORES )) && echo "AVISO: $total_errores errores hoy"Tres líneas que convierten un script confiado en uno defensivo. En 03-04 les daremos la forma de if que corresponde y las integraremos en el fichero.
Errores Comunes y Consejos
- Olvidar los espacios interiores de los corchetes.
[[-f $f]]no es sintaxis:[y[[son palabras que necesitan espacios a ambos lados. - Comparar números con
>o<.[[ "100" > "9" ]]es falso. Usa-gto, mejor aún,(( )). - Usar
-eqcon texto.[[ "Valencia" -eq "Sevilla" ]]dase esperaba una expresión entera. - Usar
[ ]con variables sin comillas. Si la variable está vacía, eltestrecibe menos argumentos de los que espera y falla con un mensaje críptico. - Escribir
<o>dentro de[ ]sin escapar. Bash lo entiende como redirección y crea un fichero con ese nombre. - Encadenar
a && b || ccreyendo que esif/else. Sibfalla,ctambién se ejecuta. - Confiar en
(( ))con resultado cero. Devuelve código 1 y puede abortar el script si hayset -e. - Usar
-ay-odentro de[ ]. Obsoletos y ambiguos: usa&&y||dentro de[[ ]].
Ejercicios
Ejercicio 1 — Traduce a operadores. Escribe, sin usar if, la línea que corresponde a cada frase: (a) si envios.csv no existe, imprime un error en stderr y sale con código 3; (b) crea ~/veloz-ops/logs solo si no existe; (c) si el número de incidencias supera 100, imprime una alerta; (d) si la variable ciudad está vacía, asígnale Valencia (usando operadores, no expansión de parámetros).
Ejercicio 2 — Encuentra los cinco fallos. Este bloque tiene cinco errores de operadores. Identifícalos y reescríbelo con las mejores prácticas de la lección.
errores=`grep -c ERROR /var/log/veloz/app.log`
if [-f /srv/veloz/datos/envios.csv]
[ $errores > 50 ] && echo "muchos errores"
[ -f $csv -a -r $csv ] && echo "legible"
[[ $ciudad -eq "Valencia" ]] && echo "es Valencia"Ejercicio 3 — Validación completa. Escribe el bloque de validaciones previas de informe-diario.sh usando solo operadores. Debe comprobar, con un código de salida distinto para cada fallo: que el log existe y es legible (código 3), que el CSV existe y no está vacío (código 4), que el directorio de informes existe o puede crearse (código 5), y que hay espacio libre en /srv (código 6). Cada error debe ir a stderr.
Soluciones
Solución al Ejercicio 1
[[ -f "$RUTA_CSV" ]] || { echo "No encuentro $RUTA_CSV" >&2; exit 3; } # (a)
[[ -d "$DIR_INFORMES" ]] || mkdir -p "$DIR_INFORMES" # (b)
(( incidencias > 100 )) && echo "ALERTA: $incidencias incidencias hoy" # (c)
[[ -z $ciudad ]] && ciudad="Valencia" # (d)En (a) las llaves son imprescindibles: agrupan el mensaje y la salida en una sola unidad para el ||. En (c) se usa (( )) en lugar de [[ $incidencias -gt 100 ]]; ambas son correctas, pero la aritmética se lee mejor y no admite confusión con la comparación textual. En (d), la forma idiomática real de Bash es ciudad=${ciudad:-Valencia}, que verás en 03-06: hace lo mismo en una expresión.
Solución al Ejercicio 2
Los cinco fallos: (1) comillas invertidas en lugar de $( ); (2) [-f ...] sin espacios interiores y sin cerrar el if —además falta el ; then—; (3) > compara texto y encima crea un fichero llamado 50, hay que usar -gt o (( )); (4) -a obsoleto y variables sin comillas dentro de [ ]; (5) -eq aplicado a texto, que dará se esperaba una expresión entera.
errores=$(grep -c ERROR /var/log/veloz/app.log)
[[ -f /srv/veloz/datos/envios.csv ]] && echo "el CSV existe"
(( errores > 50 )) && echo "muchos errores"
[[ -f $csv && -r $csv ]] && echo "legible"
[[ $ciudad == "Valencia" ]] && echo "es Valencia"El fallo (3) merece una mirada: con [ $errores > 50 ], Bash interpreta > como redirección, evalúa [ $errores ] (verdadero si la variable no está vacía) y crea un fichero vacío llamado 50 en el directorio actual. El script parece funcionar —siempre dice «muchos errores»— y va dejando basura por el sistema de ficheros. Es el ejemplo perfecto de por qué los operadores importan.
Solución al Ejercicio 3
[[ -f "$RUTA_APP_LOG" && -r "$RUTA_APP_LOG" ]] \
|| { echo "ERROR: no puedo leer $RUTA_APP_LOG" >&2; exit 3; }
[[ -f "$RUTA_CSV" && -s "$RUTA_CSV" ]] \
|| { echo "ERROR: $RUTA_CSV no existe o está vacío" >&2; exit 4; }
[[ -d "$DIR_INFORMES" ]] || mkdir -p "$DIR_INFORMES" \
|| { echo "ERROR: no puedo crear $DIR_INFORMES" >&2; exit 5; }
libre=$(df --output=avail -m /srv | tail -1)
(( libre > 100 )) || { echo "ERROR: menos de 100 MB libres en /srv" >&2; exit 6; }Tres detalles a destacar. El uso de -s además de -f en la segunda comprobación es lo que evita el informe silencioso con todo a cero. La tercera línea encadena || dos veces: si el directorio no existe intenta crearlo, y si eso también falla, aborta; es correcto porque el segundo || recoge el fallo del mkdir. Y los códigos de salida distintos (3, 4, 5, 6) permiten que quien invoque el script —cron, otro script, un panel de monitorización— sepa qué falló sin leer los mensajes, que es justo el contrato del que hablábamos en 03-01.
Conclusión
Ya tienes el vocabulario con el que un script razona. Distingues las cuatro familias de operadores y sabes que > significa cosas distintas según el contexto; encadenas comandos con ;, && y || aprovechando el cortocircuito, y dominas el idioma comando || exit 1; agrupas con { ...; } y con ( ... ) cuando quieres aislamiento; conoces las tres formas de test y por qué [[ ]] es la elección correcta en Bash; separas la comparación numérica de la textual, que es el error más caro de los principiantes; verificas ficheros con -f, -r, -s, -d y -nt, entendiendo por qué -s previene fallos silenciosos; combinas condiciones dentro de [[ ]] en lugar de los obsoletos -a y -o; y usas (( )) con la precaución de su resultado cero.
Y, sobre todo, las tres validaciones que informe-diario.sh necesitaba ya están escritas. Pero escritas como líneas sueltas encadenadas con ||, un estilo que resiste bien dos condiciones y se vuelve ilegible con cinco.
En la lección 03-04 les damos su forma definitiva. Descubrirás que if en Bash no evalúa un booleano sino un código de salida —lo que explica por qué if grep -q ERROR ... funciona sin corchetes—, aprenderás a estructurar validaciones con cláusulas de guarda en lugar de anidamientos, y informe-diario.sh pasará a comprobar su entorno antes de trabajar y a avisar cuando los errores superen el umbral.
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
