estado-servicio.sh ya distingue proceso, puerto y aplicación, pero se queda en la puerta: sabe que /salud devuelve un 200, no qué dice. La veloz-api responde JSON —estado de la base de datos, envíos en cola, versión desplegada, métricas—, y ahí el toolkit deja de leer ficheros que alguien dejó en el disco para integrarse con el resto del sistema. Esta lección cubre curl a fondo para hablar con APIs y jq para manejar JSON de verdad: extraerlo, filtrarlo, convertirlo en columnas y también construirlo de forma segura, porque al final informe-diario.sh publicará su resumen diario en JSON.
Contenido
curlen scripts: el trío-sSf- Métodos, cabeceras y cuerpos
- Ficheros, redirecciones y tiempos de espera
- Credenciales sin dejar rastro
- Códigos HTTP: qué debe distinguir un script
- Cuerpo y código de estado en una sola llamada
- Por qué NO se parsea JSON con
greposed jq: el modelo de filtros- Seleccionar, filtrar y agregar
- Controlar la salida:
-r,-c,-ey@tsv - Construir JSON de forma segura
- Leer JSON en Bash, y JSON como configuración
- Aplicación: el toolkit habla con la API y publica JSON
curl en scripts: el trío -sSf
curl en scripts: el trío -sSfPor defecto curl está pensado para una persona en una terminal: escribe una barra de progreso y, si el servidor contesta un error, imprime la página de error y devuelve 0. En un script eso es exactamente lo contrario de lo que quieres. Tres opciones lo arreglan:
| Opción | Efecto | Por qué |
|---|---|---|
-s |
Silencioso: sin barra de progreso ni mensajes | La barra ensucia la salida y los registros |
-S |
Pero sí muestra los errores de curl |
Con -s a secas, un fallo de red sería mudo |
-f |
Falla (código 22) ante respuestas HTTP 4xx y 5xx | Sin esto, $? vale 0 aunque el servidor conteste 500 |
Memoriza -sSf como un bloque: silencioso, pero que se queje, y que falle de verdad. Es el error más extendido en scripts que hablan con APIs, y produce el peor de los fallos posibles: uno silencioso que hace creer que todo va bien.
- Métodos, cabeceras y cuerpos
Para APIs REST hacen falta cuatro opciones más:
curl -sSf -X POST -H 'Content-Type: application/json' -H "Authorization: Bearer $VELOZ_TOKEN" \
-d '{"ciudad":"Madrid","estado":"incidencia"}' http://localhost:8080/envios-X fija el método (GET, POST, PUT, DELETE); no hace falta con -d, que ya implica POST. -H añade una cabecera y se repite tantas veces como haga falta. -d envía el cuerpo —si empieza por @, lo lee de un fichero: -d @cuerpo.json—. Para parámetros con caracteres especiales existe --data-urlencode, que codifica el valor: curl -sSfG --data-urlencode "ciudad=Palma de Mallorca" http://localhost:8080/envios. Ese -G convierte los datos en parámetros de la URL en lugar de cuerpo, así que el resultado es /envios?ciudad=Palma%20de%20Mallorca. Escribir esa URL a mano con una variable de Bash es un fallo esperando ocurrir: un espacio, un & o un acento en el valor y la petición cambia de significado.
- Ficheros, redirecciones y tiempos de espera
| Opción | Qué hace |
|---|---|
-o fichero |
Guarda el cuerpo en ese fichero (-o /dev/null para tirarlo) |
-O |
Guarda con el nombre que tiene en la URL |
-L |
Sigue las redirecciones 301/302 (no lo hace por defecto) |
--connect-timeout N |
Máximo para establecer la conexión |
--max-time N |
Máximo para toda la operación |
Los dos tiempos de espera son distintos y hay que poner los dos. --connect-timeout 5 corta rápido cuando la máquina no está; --max-time 30 protege del servidor que acepta la conexión y luego se queda pensando indefinidamente. Un curl sin --max-time en un script automático es una bomba de relojería: el día que el servicio se atasque, tu tarea de cron se quedará colgada para siempre y bloqueará las siguientes (07-01).
- Credenciales sin dejar rastro
-u usuario:clave hace autenticación básica, pero escrita así la contraseña aparece en ps, en el historial (02-06) y en cualquier registro de la orden. Las dos formas correctas:
curl -sSf --netrc-file ~/.netrc https://api.veloz.example/envios # credenciales en fichero 600
curl -sSf -H "Authorization: Bearer $VELOZ_TOKEN" http://localhost:8080/metricas--netrc-file lee máquina, usuario y clave de un fichero con permisos 600, igual que veloz-ops.conf (05-06). La segunda opción usa una variable de entorno cargada desde ese fichero de configuración: no queda en el historial, aunque sí es visible en /proc/<pid>/environ para el propio usuario y root. Lo que nunca debe hacerse es incrustar la credencial en la URL, como ya se vio en 06-04.
- Códigos HTTP: qué debe distinguir un script
| Familia | Significado | Qué debe hacer el script |
|---|---|---|
| 2xx | Correcto | Continuar |
| 3xx | Redirección | Seguirla con -L, o tratarla como configuración incorrecta |
| 4xx | Tú te equivocaste (401, 403, 404, 422) | Fallar y no reintentar: reintentar no lo va a arreglar |
| 5xx | El servidor falló (500, 502, 503) | Reintentar con retroceso (06-04); puede ser temporal |
| Sin respuesta | Red, DNS, tiempo agotado | Distinguirlo de un 5xx en el mensaje |
Esa distinción entre 4xx y 5xx es la que hace útil un script de integración: reintentar un 404 es perder el tiempo, y no reintentar un 503 es dar por caído un servicio que se estaba reiniciando. Y recuerda el punto de partida: sin -f, curl devuelve 0 con cualquier código, así que sin él ni siquiera te enteras.
- Cuerpo y código de estado en una sola llamada
-f te dice que hubo error, pero descarta el cuerpo, que suele contener la explicación. Para tener ambos sin hacer dos peticiones, se pide el código con -w al final y se separa después:
respuesta=$(curl -sS -w '\n%{http_code}' --max-time 10 "http://localhost:8080/envios?ciudad=Madrid")
codigo="${respuesta##*$'\n'}" # ultima linea: el codigo
cuerpo="${respuesta%$'\n'*}" # todo lo anterior: el cuerpo
case "$codigo" in
2??) veloz_log_info "consulta correcta" ;;
4??) veloz_morir 1 "peticion incorrecta ($codigo): $cuerpo" ;;
5??) veloz_log_error "error del servidor ($codigo), reintentando"; return 1 ;;
*) veloz_morir 1 "sin respuesta de la API" ;;
esacAquí se juntan tres cosas de módulos anteriores: -w '\n%{http_code}' añade el código en una línea nueva al final, las expansiones ${var##*} y ${var%*} de 04-04 lo separan del cuerpo sin lanzar procesos, y el case con globs de 04-05 clasifica por familia con 2??. Fíjate en que aquí no se usa -f: queremos el cuerpo del error, así que la decisión la toma el case.
- Por qué NO se parsea JSON con
grep o sed
grep o sedLa tentación es enorme y el resultado siempre acaba mal. Para sacar el estado de /salud, lo que todo el mundo escribe la primera vez es curl -s $API/salud | grep -o '"estado":"[^"]*"' | cut -d'"' -f4. Funciona hasta que deja de hacerlo, y las razones no son rebuscadas: el JSON puede venir compacto o con saltos de línea (tu regex depende del formato, que el servidor puede cambiar sin avisar); el orden de las claves no está garantizado; puede aparecer un objeto anidado con otra clave estado y tu grep cogerá la primera que encuentre; un valor puede contener comillas escapadas ("mensaje":"error \"grave\"") que destrozan [^"]*; y null, los números y los booleanos no llevan comillas, así que el patrón ni los ve. En resumen: JSON no es un formato de líneas, y las herramientas de líneas no pueden entenderlo con fiabilidad. Lo que hace falta es un analizador, y ese es jq.
jq: el modelo de filtros
jq: el modelo de filtrosjq es un lenguaje de filtros: recibe un documento JSON, le aplica una expresión y emite JSON. La idea central es que todo filtro transforma una entrada en cero, una o varias salidas, y se encadenan con | como las tuberías del shell.
Supongamos que /salud devuelve {"estado":"ok","version":"2.4.1","bd":{"conectada":true,"latencia_ms":12},"cola":[3,7,2]}:
| Filtro | Resultado | Qué hace |
|---|---|---|
. |
Todo el documento formateado | Identidad; útil para leerlo |
.estado |
"ok" |
Accede a una clave |
.bd.latencia_ms |
12 |
Ruta anidada |
.cola[0] |
3 |
Índice de un array |
.cola[1:] |
[7,2] |
Rebanada |
.cola[] |
3, 7, 2 |
Itera: emite tres salidas separadas |
.cola | length |
3 |
Longitud de array, objeto o cadena |
keys |
["bd","cola","estado","version"] |
Claves ordenadas |
has("estado") |
true |
¿Existe esa clave? |
.faltante |
null |
Una clave inexistente no es un error |
.estado // "desconocido" |
"ok" |
Valor por defecto si es null o false |
.bd.x? |
(nada) | ? suprime el error si el tipo no encaja |
Las dos últimas filas son las que evitan la mitad de los sustos: // da un valor por defecto cuando la API omite un campo, y ? impide que un error de tipo aborte todo el filtro. La diferencia entre .cola (un array) y .cola[] (tres salidas) es el concepto que hay que interiorizar: la mayoría de los filtros interesantes trabajan sobre flujos de valores, no sobre uno solo.
- Seleccionar, filtrar y agregar
Sobre una respuesta de /envios?ciudad=Madrid, que devuelve un array de objetos con id, estado e importe:
curl -sSf "$API/envios?ciudad=Madrid" | jq '.[] | select(.estado == "incidencia")'
curl -sSf "$API/envios?ciudad=Madrid" | jq '[.[] | .importe] | add'| Filtro | Qué hace |
|---|---|
select(cond) |
Deja pasar solo los elementos que cumplen la condición |
map(f) |
Aplica f a cada elemento de un array ([.[] | f] abreviado) |
add |
Suma los elementos de un array (o concatena) |
length |
Número de elementos, claves o caracteres |
sort_by(.campo) |
Ordena un array por ese campo |
min / max / min_by(.c) |
Extremos |
group_by(.campo) |
Agrupa en un array de arrays (requiere ordenar por el mismo campo) |
to_entries |
Convierte {"a":1} en [{"key":"a","value":1}] para poder iterar objetos |
Los corchetes de [.[] | .importe] son importantes: .[] | .importe emite tres valores sueltos, y add necesita un array, así que hay que recogerlos. Confundir "flujo de valores" con "array" es el error número uno con jq, y se arregla siempre con [ ] o con map(). Verás que group_by + map es la misma agregación que hacías con arrays asociativos en awk (06-01), solo que sobre JSON.
- Controlar la salida:
-r, -c, -e y @tsv
-r, -c, -e y @tsvPor defecto jq emite JSON, así que una cadena sale entrecomillada: "ok", no ok. Al asignarla a una variable de Bash, esas comillas viajan con ella y estropean cualquier comparación posterior.
| Opción | Efecto |
|---|---|
-r |
Raw: emite las cadenas sin comillas |
-c |
Compacto: cada resultado en una sola línea |
-e |
El código de salida refleja el resultado: 1 si fue null o false |
-n |
No lee entrada; construye desde cero (sección 11) |
estado=$(curl -sSf "$API/salud" | jq -r '.estado') # "ok" -> ok
[[ "$estado" == ok ]] || veloz_log_error "API en estado $estado"
curl -sSf "$API/salud" | jq -e '.bd.conectada' >/dev/null || veloz_log_error "BD desconectada"-e convierte una consulta JSON en una condición de shell, sin variables intermedias. Y para volver al terreno de awk, @tsv y @csv convierten arrays en columnas:
curl -sSf "$API/envios?ciudad=Madrid" | jq -r '.[] | [.id, .estado, .importe] | @tsv' |
awk -F'\t' '{ s[$2] += $3 } END { for (e in s) printf "%-12s %8.2f\n", e, s[e] }'Ese encadenamiento es el puente entre los dos mundos: jq entiende la estructura y la aplana a columnas; awk agrega. @csv entrecomilla los campos según las reglas de CSV, y ambos requieren -r para que no salga todo como una cadena JSON escapada.
- Construir JSON de forma segura
Generar JSON a mano —printf '{"ciudad":"%s","nota":"%s"}\n' "$ciudad" "$nota"— funciona hasta el primer valor con una comilla, una barra invertida o un salto de línea, y entonces produce JSON inválido, o peor, JSON válido con el contenido cambiado. La forma correcta es jq -n, que construye desde cero, con --arg para cadenas y --argjson para valores que ya son JSON (números, booleanos, objetos):
jq -n --arg fecha "$FECHA" --arg host "$(hostname)" \
--argjson total "$total" --argjson tasa "$tasa" \
'{fecha: $fecha, host: $host, total: $total, tasa_entrega: $tasa}'
# -> {"fecha":"2026-08-03","host":"srv-veloz-01","total":1001,"tasa_entrega":87.3}jq se encarga del escapado: una comilla dentro de $nota sale como \", un salto de línea como \n y los acentos se codifican correctamente. La distinción entre --arg y --argjson es la trampa habitual: --arg total 1001 produce "1001" (cadena) y --argjson total 1001 produce 1001 (número). Si el valor puede venir vacío, --argjson fallará —jq no acepta JSON inválido—, así que conviene un ${total:-0}. Y como con awk -v en 06-01, el principio es el mismo: los valores de Bash entran como parámetros, nunca interpolados dentro del filtro, porque interpolarlos es inyección de código.
- Leer JSON en Bash, y JSON como configuración
Para recorrer una respuesta en Bash, la combinación es jq -r '.[] | @tsv' con el bucle de 04-01:
while IFS=$'\t' read -r id estado importe; do
[[ "$estado" == incidencia ]] && veloz_log_error "envio $id con incidencia ($importe EUR)"
done < <(curl -sSf "$API/envios?ciudad=Madrid" | jq -r '.[] | [.id, .estado, .importe] | @tsv')@tsv es preferible a separar por espacios porque los valores pueden contenerlos, y también escapa los tabuladores y saltos de línea que hubiera dentro de un campo. La sustitución de procesos < <( ) de 05-05 mantiene el bucle en el shell actual. Cuando lo que necesitas es un array de Bash con una sola columna, mapfile -t ids < <(jq -r '.[].id' <<< "$json") (04-03) es más directo.
JSON también sirve como formato de configuración, y jq lo lee con las mismas reglas: jq -r '.umbrales.disco // 85' etc/veloz.json devuelve el valor o el defecto si falta. Frente al clave=valor de veloz-ops.conf (05-06), gana en estructura anidada y tipos, y pierde en que no se puede cargar con source y no admite comentarios. Para YAML —el formato de los ficheros de CI y de Kubernetes— existe yq, que replica la sintaxis de jq sobre YAML e incluso convierte entre ambos con yq -o=json. Si ya sabes jq, sabes yq.
- Aplicación: el toolkit habla con la API y publica JSON
Primero, estado-servicio.sh consulta la API de verdad y distingue caída de error de aplicación, que es la diferencia entre reiniciar el servicio y avisar al equipo de desarrollo:
comprobar_api_json() {
local api="http://localhost:8080" cuerpo codigo salida
salida=$(curl -sS -w '\n%{http_code}' --connect-timeout 3 --max-time 10 "$api/salud") || {
veloz_log_error "veloz-api: sin respuesta (red, DNS o servicio caido)"; return 1; }
codigo="${salida##*$'\n'}"; cuerpo="${salida%$'\n'*}"
[[ "$codigo" == 2?? ]] || {
veloz_log_error "veloz-api: HTTP $codigo — $(jq -r '.mensaje // "sin detalle"' <<< "$cuerpo")"
return 1; }
jq -e '.estado == "ok"' <<< "$cuerpo" >/dev/null ||
veloz_log_error "veloz-api: viva pero degradada ($(jq -r '.estado' <<< "$cuerpo"))"
veloz_log_info "veloz-api $(jq -r '.version' <<< "$cuerpo") — BD $(jq -r 'if .bd.conectada then "ok" else "KO" end' <<< "$cuerpo")"
}Los tres desenlaces son distintos a propósito: sin respuesta (el curl falla), respuesta con error HTTP (se extrae .mensaje del cuerpo para el aviso) y respuesta correcta pero con estado degradado. jq -e usa el JSON como condición y el if ... then ... else ... end de jq formatea el booleano para el mensaje. Nótese <<< "$cuerpo", el here-string de 05-05: evita releer la respuesta de la red cada vez.
Y informe-diario.sh cierra el círculo publicando su resumen como JSON, construido con jq -n:
publicar_resumen() {
local destino="$BASE_DIR/logs/resumen-$(date +%F).json" tmp
tmp=$(mktemp) && trap 'rm -f "$tmp"' RETURN
jq -n --arg fecha "$(date +%F)" --arg host "$(hostname -f)" \
--argjson total "${total:-0}" --argjson tasa "${tasa:-0}" \
'{generado: (now | todate), fecha: $fecha, host: $host,
resumen: {envios: $total, tasa_entrega: $tasa}}' > "$tmp" && mv "$tmp" "$destino"
veloz_log_info "resumen publicado en $destino"
}El mktemp con trap de 05-01 y el mv final garantizan que nadie lea nunca un fichero a medio escribir: o existe el resumen completo o existe el del día anterior. now | todate genera la marca de tiempo en UTC ISO-8601 sin llamar a date. Y ahora el informe ya no es solo un texto para leer: es un dato que otro programa —un panel, una alerta, el proyecto 09-05— puede consumir sin volver a parsear nada.
Errores Comunes y Consejos
curlsin-f(o sin comprobar el código). Devuelve 0 con un 500 y el script continúa como si nada. Usa-sSf, o-w '%{http_code}'y uncase.curlsin--max-time. Un servicio atascado cuelga la tarea de cron indefinidamente.- Parsear JSON con
grep/sed. Falla con el formato compacto, el orden de claves, los anidados y las comillas escapadas. Usajq. - Olvidar
-r.estado=$(jq '.estado')guarda"ok"con comillas y[[ $estado == ok ]]falla. - Confundir flujo con array.
.[] | .importeemite valores sueltos;addnecesita[.[] | .importe]. --argpara números. Produce"1001"en vez de1001; para números y booleanos,--argjson.- Interpolar variables de Bash dentro del filtro
jq. Mismo riesgo de inyección que en awk: usa--arg. - Reintentar un 4xx. No se va a arreglar solo. Reintenta los 5xx y los fallos de red, con retroceso (06-04).
- Consejo: construye los filtros por pasos (
jq '.', luegojq '.campo'…) contra una respuesta de ejemplo guardada en un fichero: es más rápido que llamar a la API, funciona sin conexión yjqse depura mucho mejor en incrementos que de un tirón.
Ejercicios
Ejercicio 1. Escribe una función veloz_api_get para lib/comun.sh que haga GET a una ruta de la API, devuelva el cuerpo por la salida estándar, distinga red / 4xx / 5xx con mensajes distintos y códigos de salida diferentes, y no pueda colgarse.
Ejercicio 2. Con /metricas devolviendo {"peticiones":48210,"errores":37,"latencia_p95_ms":210,"envios_cola":14}, escribe una comprobación que avise si la tasa de errores supera el 0,1 % o si la latencia p95 pasa de 500 ms, tratando los campos que falten como 0.
Ejercicio 3. Convierte la salida de /envios?ciudad=Madrid (array de objetos con id, repartidor, estado, importe) en un resumen por repartidor con número de envíos e importe total, ordenado de mayor a menor, usando solo jq.
Soluciones
Solución 1.
# veloz_api_get — GET a la API. Uso: veloz_api_get <ruta>. Devuelve el cuerpo por stdout.
veloz_api_get() {
local ruta="${1:?falta la ruta}" base="${VELOZ_API:-http://localhost:8080}" salida codigo
salida=$(curl -sS -w '\n%{http_code}' --connect-timeout 3 --max-time 15 "$base$ruta") ||
{ veloz_log_error "API: sin respuesta en $ruta"; return 69; }
codigo="${salida##*$'\n'}"
case "$codigo" in
2??) printf '%s\n' "${salida%$'\n'*}" ;;
4??) veloz_log_error "API: peticion incorrecta ($codigo) en $ruta"; return 64 ;;
*) veloz_log_error "API: error del servidor ($codigo) en $ruta"; return 75 ;;
esac
}Los tres códigos son deliberados y siguen la tabla de 05-03: 69 (EX_UNAVAILABLE) cuando no hay respuesta, 64 (EX_USAGE) para un 4xx —el error es nuestro— y 75 (EX_TEMPFAIL) para un 5xx, que es precisamente el código que le dice al llamante "esto es temporal, puedes reintentar". El cuerpo sale limpio por stdout y los diagnósticos por stderr (02-04), de modo que datos=$(veloz_api_get /salud) captura solo lo que interesa.
Solución 2.
metricas=$(veloz_api_get /metricas) || exit $?
read -r peticiones errores p95 < <(jq -r '[.peticiones // 0, .errores // 0,
.latencia_p95_ms // 0] | @tsv' <<< "$metricas")
awk -v e="$errores" -v p="$peticiones" 'BEGIN { exit !(p > 0 && 100*e/p > 0.1) }' &&
veloz_log_error "tasa de errores por encima del 0.1% ($errores de $peticiones)"
(( p95 > 500 )) && veloz_log_error "latencia p95 de ${p95}ms por encima del umbral"El // 0 de cada campo evita que una métrica ausente se convierta en null y rompa las cuentas. Los tres valores se extraen en una sola invocación de jq con @tsv y un read, en vez de llamar tres veces. La tasa de errores es decimal, así que la comparación va en awk con el idioma exit !(...) de 06-03; la latencia es entera y le basta (( )).
Solución 3.
veloz_api_get "/envios?ciudad=Madrid" |
jq -r 'group_by(.repartidor)
| map({repartidor: .[0].repartidor, envios: length, total: (map(.importe) | add)})
| sort_by(-.total)
| .[] | [.repartidor, .envios, (.total | tostring)] | @tsv' |
column -tgroup_by produce un array de arrays —uno por repartidor—, y map lo convierte en objetos: .[0].repartidor toma el nombre de cualquier elemento del grupo, length cuenta y map(.importe) | add suma. sort_by(-.total) ordena descendente negando el valor, más limpio que ordenar y luego reverse. Al final, @tsv aplana a columnas y column -t (02-02) las alinea. Es exactamente la agregación de la solución 1 de 06-01, pero partiendo de JSON en vez de CSV.
Conclusión
Hablar con una API desde un script son dos herramientas y unas cuantas reglas. De curl: -sSf como bloque indivisible —silencioso, quejica ante fallos de red y fallando de verdad ante un error HTTP, porque sin -f devuelve 0 con un 500—; -X, -H, -d y --data-urlencode para construir la petición; -L, -o/-O para el cuerpo; --connect-timeout y --max-time siempre, o el día que el servicio se atasque colgarás la tarea de cron; credenciales por --netrc o cabecera desde un fichero 600, nunca en la URL; y -w '\n%{http_code}' para quedarte con cuerpo y código en una sola llamada y clasificar con un case que distinga 4xx (no reintentar) de 5xx (reintentar con retroceso). De jq: un lenguaje de filtros encadenados con | donde .campo, .a.b, .[], select, map, group_by, add, sort_by, length, to_entries, // y ? cubren casi todo; -r para sacar cadenas sin comillas, -e para usar JSON como condición, @tsv/@csv para volver al terreno de awk; y jq -n --arg/--argjson para construir JSON con el escapado resuelto, pasando los valores de Bash como parámetros y nunca interpolados. Y la regla que engloba todo: JSON no es un formato de líneas, así que grep y sed no sirven para leerlo.
Con esto se cierra el Módulo 6 y el toolkit de Veloz Envíos cambia de naturaleza. informe-diario.sh agrega en una sola pasada con awk y publica su resumen en JSON; estado-servicio.sh reúne el contexto del sistema, comprueba red y puerto, interroga a la veloz-api y distingue una caída de un error de aplicación. Ya no es un lector de ficheros: es una pieza integrada con el resto del sistema. Pero fíjate en lo que tienen en común todas las ejecuciones de este módulo: has sido tú quien ha escrito el comando. El informe diario solo existe si alguien se acuerda de lanzarlo, y una comprobación de estado que se ejecuta cuando ya sospechas que algo va mal llega tarde por definición. En el Módulo 7 eso se acaba: cron para que las tareas se ejecuten solas (07-01), el diseño de tareas verdaderamente desatendidas (07-02), respaldos automáticos (07-03), monitorización y registro continuos (07-04), servicios y temporizadores de systemd (07-05) y automatización remota con ssh (07-06). El toolkit deja de esperar tus órdenes.
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
