Al final de 08-01 sustituimos un bucle while read por un único awk afirmando que era «más rápido». Es una afirmación cómoda y muy repetida, pero mientras no haya un número al lado no es ingeniería, es folclore. Esta lección va de números: cómo se miden de verdad los tiempos de un script, qué es lo que de verdad cuesta caro en Bash —y no es lo que la mayoría cree—, qué optimizaciones tienen un efecto medible y cuáles son superstición, y sobre todo cuándo dejar de optimizar. Al final perfilaremos informe-diario.sh sobre un CSV de 500.000 envíos y reduciremos su tiempo de ejecución de minutos a segundos, midiendo antes y después.
Contenido
- Primero medir, después optimizar
- Las tres formas de medir:
time,SECONDSydate +%s%N - El coste dominante en Bash: crear procesos
- Builtins frente a órdenes externas
- Tuberías inútiles y leer el fichero una sola vez
- El coste oculto de las subshells
- Acumular la salida y escribir una vez
while read,mapfileoawk: cuándo cada uno- Paralelizar de verdad, y cuándo no sirve
LC_ALL=C, memoria y el criterio final- Aplicación: perfilar
informe-diario.sh
- Primero medir, después optimizar
Hay tres razones para no optimizar sin medir. La primera es que la intuición falla: casi todo el mundo cree que el bucle es lento por ser un bucle, cuando en realidad lo es por los dos cut que hay dentro. La segunda es que optimizar cuesta legibilidad, y acabas de dedicar una lección entera a ganarla; pagarla a cambio de un 2 % de mejora es un mal negocio. La tercera es que el 90 % del tiempo suele estar en el 10 % del código: si no sabes cuál es ese 10 %, optimizarás el resto.
El método es siempre el mismo, cuatro pasos:
- Establece la línea base. Mide el script tal como está, sobre datos representativos.
- Localiza el punto caliente. Mide por partes hasta saber qué bloque se lleva el tiempo.
- Cambia una sola cosa y vuelve a medir en las mismas condiciones.
- Para cuando sea suficiente. Si el informe diario ya tarda 4 segundos y se ejecuta una vez al día, bajarlo a 2 no le sirve a nadie.
Y una advertencia sobre los «datos representativos»: medir con el CSV de pruebas de 50 líneas no dice absolutamente nada. Muchas optimizaciones solo se notan a partir de cierto tamaño, y algunas —cargar el fichero en memoria— son buenas con 1.000 líneas y catastróficas con diez millones.
- Las tres formas de medir:
time, SECONDS y date +%s%N
time, SECONDS y date +%s%NLas tres cifras cuentan historias distintas y saber leerlas es media lección:
| Cifra | Qué mide | Qué significa si es alta |
|---|---|---|
real |
Tiempo de reloj transcurrido | Lo que sufre el usuario; incluye esperas de disco y de red |
user |
CPU gastada en tu código y el de las órdenes | Cálculo puro: bucles, awk, sort |
sys |
CPU gastada dentro del núcleo | Muchas llamadas al sistema: crear procesos, abrir ficheros |
La combinación diagnóstica es esta: si user + sys es muy inferior a real, el script está esperando (disco, red, un sleep) y optimizar el código no servirá de nada. Y si sys es alto y comparable a user, como en el ejemplo, el culpable casi siempre es la creación de procesos: 24 segundos dentro del núcleo no los gasta un cálculo, los gasta un fork por línea.
Cuidado con un detalle: time es una palabra clave del shell y una orden externa (/usr/bin/time), con salidas distintas. La del shell es la que has visto; la externa, con -v, añade memoria máxima usada, que es muy útil. Para medir partes de un script, dos herramientas ya conocidas:
SECONDS=0 # variable magica de Bash (04-06)
procesa_envios "$fichero"
veloz_log_info "procesado en ${SECONDS}s" # resolucion de 1 segundo
inicio=$(date +%s%N) # nanosegundos
veloz_porcentaje 412 1284 >/dev/null
printf 'tardo %d ms\n' $(( ($(date +%s%N) - inicio) / 1000000 ))SECONDS es gratis (es interno) pero solo da segundos enteros: sirve para bloques largos. date +%s%N da nanosegundos y sirve para bloques cortos, aunque crea un proceso por medición, así que no lo pongas dentro del bucle que estás midiendo. Y una regla de laboratorio: repite la medición tres veces y quédate con la mediana. La primera ejecución llena la caché de disco y siempre es la más lenta; comparar una primera ejecución con una tercera es la forma más habitual de «demostrar» una mejora inexistente.
- El coste dominante en Bash: crear procesos
Esta es la idea de la lección. En Bash, ejecutar una orden externa implica que el núcleo duplique el proceso (fork), cargue el binario (exec), resuelva sus bibliotecas y lo destruya al terminar. Eso cuesta del orden de 1 a 3 milisegundos. Parece nada, hasta que lo multiplicas por el número de líneas de un fichero.
Comparemos tres formas de extraer la ciudad (campo 3) de cada línea de un CSV de 500.000 envíos:
# A) Un proceso externo por linea: el anti-patron
while IFS= read -r linea; do
ciudad=$(echo "$linea" | cut -d, -f3)
(( conteo[$ciudad]++ ))
done < envios.csv
# B) Sin procesos: separacion por IFS, todo dentro del shell (04-01)
while IFS=, read -r id fecha ciudad resto; do
(( conteo[$ciudad]++ ))
done < envios.csv
# C) Un unico proceso para todo el fichero (06-01)
awk -F, 'NR > 1 { c[$3]++ } END { for (x in c) print x, c[x] }' envios.csvLos tiempos medidos sobre el mismo fichero y la misma máquina:
| Versión | real |
Procesos creados | Factor |
|---|---|---|---|
A) echo | cut por línea |
21 min 40 s | 1.000.000 | ×1 |
B) read con IFS=, |
12,4 s | 0 | ×105 |
C) awk de una pasada |
0,9 s | 1 | ×1.400 |
Un millón de procesos para leer medio millón de líneas. La versión A no es «un poco más lenta»: es inviable. Y fíjate en que la diferencia entre A y B no es el bucle, que es el mismo, sino haber sacado los dos procesos de dentro. Esta es la primera regla operativa: nunca ejecutes una orden externa dentro de un bucle que recorre líneas. Si la necesitas, probablemente el bucle entero deba ser un awk.
- Builtins frente a órdenes externas
Corolario de lo anterior: cuando el shell puede hacer algo por sí mismo, hazlo con él. type -a (01-04) te dice cuál es cuál.
| En vez de (externo) | Usa (interno) | Nota |
|---|---|---|
test, [ ] |
[[ ]] |
En Bash [ también es builtin, pero [[ ]] no expande |
expr $a + $b |
$(( a + b )) |
expr es un fósil; $(( )) es interno (04-06) |
echo con opciones |
printf |
printf es builtin y además portable (03-06) |
wc -c <<< "$s" |
${#s} |
Longitud sin proceso ni here-string |
basename "$f" |
${f##*/} |
Expansión de parámetro (04-04) |
dirname "$f" |
${f%/*} |
Idem |
echo "$s" | tr a-z A-Z |
${s^^} |
Bash 4+ (04-04) |
echo "$s" | sed 's/a/b/' |
${s/a/b} |
Para casos simples |
cat fichero (una vez) |
$(< fichero) |
Bash lee el fichero sin lanzar cat |
seq 1 100 |
{1..100} |
Expansión de llaves |
Aquí conviene una dosis de honestidad: fuera de un bucle, esto es irrelevante. Cambiar un basename suelto por ${f##*/} ahorra dos milisegundos en todo el script y puede empeorar la legibilidad para quien no domine las expansiones. La tabla importa dentro de bucles, donde el ahorro se multiplica por el número de vueltas. Optimiza el interior de los bucles y deja el exterior legible.
- Tuberías inútiles y leer el fichero una sola vez
Cada | de una tubería es un proceso más. La mayoría de las tuberías largas que se ven en producción tienen etapas que sobran:
cat app.log | grep ERROR | wc -l # 3 procesos
grep -c ERROR app.log # 1 proceso, mismo resultado
cat envios.csv | awk -F, '{print $3}' # 2 procesos
awk -F, '{print $3}' envios.csv # 1 proceso, awk abre ficheros
grep ERROR app.log | awk '{print $4}' # 2 procesos y dos pasadas
awk '/ERROR/ {print $4}' app.log # 1 proceso, awk ya filtra
sort f | uniq | sort -rn # ordena dos veces
sort -u f | sort -rn # una menosEl caso del cat inútil tiene incluso nombre (useless use of cat). Pero el error más caro no es contar procesos: es leer el mismo fichero varias veces. Este patrón aparece en todos los scripts de informes:
# MAL: cuatro pasadas completas sobre 500.000 lineas
total=$(wc -l < envios.csv)
entregados=$(grep -c ',entregado,' envios.csv)
incidencias=$(grep -c ',incidencia,' envios.csv)
valencia=$(awk -F, '$3 == "Valencia"' envios.csv | wc -l)
# BIEN: una sola pasada, cuatro resultados (06-01)
read -r total entregados incidencias valencia < <(awk -F, '
NR > 1 { total++
if ($5 == "entregado") entregados++
if ($5 == "incidencia") incidencias++
if ($3 == "Valencia") valencia++ }
END { print total+0, entregados+0, incidencias+0, valencia+0 }' envios.csv)La primera versión lee 500.000 líneas cuatro veces; la segunda, una. En el toolkit real esto bajó el bloque de estadísticas de 3,2 s a 0,8 s. Y la versión de una pasada es además más consistente: si el fichero cambia mientras se ejecuta, las cuatro cifras de arriba pueden no cuadrar entre sí.
- El coste oculto de las subshells
$( ) crea una subshell, que es un proceso hijo con toda su memoria copiada. Cuesta lo mismo que lanzar una orden externa, aunque dentro no haya ninguna:
for id in "${ids[@]}"; do
fecha=$(date -d "@$marca" +%F) # 2 procesos por vuelta: subshell + date
done
printf -v fecha '%(%F)T' "$marca" # 0 procesos: formato de fecha interno (Bash 4.2+)Lo mismo ocurre con las tuberías: cmd | while read ... ejecuta el while en una subshell, y por eso las variables que modificas dentro no existen al salir —el problema clásico que resolvimos en 05-05 con done < <(cmd)—. Ahí la sustitución de proceso no es solo cuestión de corrección: también ahorra un proceso.
Un truco útil para bloques que escriben mucho: en lugar de agrupar con ( ... ) > salida.txt, que crea subshell, agrupa con llaves, que no la crea:
- Acumular la salida y escribir una vez
Cada >> dentro de un bucle abre el fichero, escribe y lo cierra. Con 500.000 vueltas son 500.000 aperturas:
# MAL: una apertura por linea
for envio in "${envios[@]}"; do
printf '%s\n' "$envio" >> informe.txt
done
# BIEN: acumula en un array y escribe una vez (04-03)
lineas=()
for envio in "${envios[@]}"; do
lineas+=( "$envio" )
done
printf '%s\n' "${lineas[@]}" > informe.txt
# MEJOR AUN: redirige el bucle entero, una sola apertura y sin memoria extra
for envio in "${envios[@]}"; do
printf '%s\n' "$envio"
done > informe.txtLas tres producen el mismo fichero; la tercera es la más rápida, la más corta y la que menos memoria usa, porque el descriptor se abre una vez y el bucle escribe en el flujo (05-05). Medido con 200.000 líneas: 4,1 s la primera, 0,9 s la segunda, 0,7 s la tercera.
while read, mapfile o awk: cuándo cada uno
while read, mapfile o awk: cuándo cada uno| Técnica | Memoria | Velocidad | Cuándo usarla |
|---|---|---|---|
while IFS= read -r l |
Constante | Media | Ficheros grandes, procesado línea a línea, lógica que necesita el shell |
mapfile -t arr < f |
Todo el fichero en RAM | Alta | Ficheros pequeños (< 50.000 líneas) que necesitas recorrer varias veces o indexar |
awk '...' f |
Constante (salvo arrays) | Muy alta | Filtrar, contar, agregar, calcular: casi todo lo que hace un informe |
La regla práctica, en una frase: si dentro del while solo filtras, cuentas o sumas, tu bucle es un awk mal escrito. El while read se justifica cuando cada línea dispara acciones que awk no sabe hacer bien —lanzar un curl por envío, invocar funciones del shell, escribir en ficheros distintos—; y aun así conviene preguntarse si awk puede generar la lista y el bucle limitarse a actuar sobre ella.
- Paralelizar de verdad, y cuándo no sirve
Cuando el trabajo es independiente por elemento y cada uno tarda de verdad —consultar la API por cada ciudad, comprimir cada rotado—, la paralelización es la mejora más grande disponible. Con xargs -P (05-01) o con &/wait (05-02):
# 8 procesos en paralelo, uno por fichero, con nombres seguros (-0)
find /var/log/veloz -name '*.log.1' -print0 \
| xargs -0 -P 8 -n 1 gzip -9
# Version con & y wait, cuando hace falta logica de shell por elemento
for ciudad in Valencia Sevilla Bilbao Madrid; do
veloz_api_get "/envios?ciudad=$ciudad" > "$tmp/$ciudad.json" &
done
waitDos advertencias imprescindibles. La primera: si el cuello de botella es el disco, paralelizar empeora las cosas. Ocho procesos leyendo a la vez de un disco mecánico generan búsquedas aleatorias y pueden tardar más que uno solo; en SSD el efecto es menor pero el límite existe igual. Mira el time: si real es alto pero user + sys es bajo, estás esperando E/S y más procesos no van a ayudar. La segunda: los procesos paralelos escriben entremezclado; que cada uno escriba en su propio fichero y se consolide al final, como hacía flota.sh en 07-06. Un punto de partida razonable para el grado de paralelismo es -P "$(nproc)" en tareas de CPU (06-03), y bastante menos en tareas de disco.
LC_ALL=C, memoria y el criterio final
LC_ALL=C, memoria y el criterio finalUn acelerador que casi nadie conoce: la configuración regional. Con es_ES.UTF-8, sort y grep aplican reglas de ordenación y clases de caracteres multibyte; con LC_ALL=C comparan byte a byte.
$ time sort envios.csv > /dev/null # real 0m9,84s
$ time LC_ALL=C sort envios.csv > /dev/null # real 0m2,31sCuatro veces más rápido por una variable. La condición es que el orden byte a byte te sirva: para claves internas, identificadores o rutas, sí; para una lista de ciudades que verá un humano, no, porque Ávila acabará fuera de sitio. En el toolkit se aplica a los ordenamientos intermedios y se deja la configuración regional en la presentación final.
Sobre la memoria, una regla simple: no cargues en memoria lo que puedes recorrer en flujo. mapfile -t lineas < /var/log/veloz/acceso.log con un log de un gigabyte intenta reservar más de un gigabyte de RAM —los arrays de Bash tienen bastante sobrecarga por elemento— y puede acabar con el proceso muerto por el gestor de memoria del núcleo, de madrugada y sin explicación. Los flujos (while read, tuberías, awk) usan memoria constante independientemente del tamaño del fichero.
Y el criterio final, el más importante de todos: saber cuándo el problema ya no es de Bash. Señales concretas de que has llegado al límite:
- Tras aplicar todo lo anterior, el script sigue tardando minutos.
- El grueso de la lógica ya está dentro de un
awkde cuarenta líneas con su propia estructura de datos. - Necesitas estructuras que Bash no tiene: números en coma flotante en serio, arrays de dos dimensiones, JSON anidado que
jqya no cubre con comodidad. - Necesitas cruzar dos ficheros grandes por una clave, ordenar por varios criterios o consultar histórico: eso es una base de datos, aunque sea SQLite.
- El script pasa de 500 líneas y su lógica de negocio importa más que su lógica de sistema.
Bash es excelente pegando órdenes del sistema; es mal lenguaje para cálculo intensivo. Reconocerlo a tiempo es una decisión técnica, no una derrota: informe-diario.sh sigue siendo Bash, pero si mañana hay que cruzar envíos con facturación e histórico de tres años, ese trozo se escribe en Python o en SQL y Bash lo orquesta.
- Aplicación: perfilar
informe-diario.sh
informe-diario.shGeneramos un CSV representativo y medimos la línea base:
$ awk 'BEGIN { print "id_envio,fecha,ciudad,repartidor,estado,importe"
srand(); c["1"]="Valencia"; c["2"]="Sevilla"; c["3"]="Bilbao"; c["4"]="Madrid"
e["1"]="entregado"; e["2"]="en_reparto"; e["3"]="incidencia"
for (i = 1; i <= 500000; i++)
printf "E%06d,2026-08-03,%s,alopez,%s,%.2f\n", i,
c[int(rand()*4)+1], e[int(rand()*3)+1], rand()*90+5
}' > /tmp/envios-grande.csv
$ time ./informe-diario.sh -f /tmp/envios-grande.csv > /dev/null
real 3m12,4s user 1m28,1s sys 1m36,8ssys casi igual a user: el diagnóstico del apartado 3 es inmediato, esto son procesos. Instrumentamos el script con SECONDS alrededor de cada bloque para localizar el punto caliente:
lectura y conteo .......... 188 s <-- aqui esta todo top de repartidores ........ 3 s generacion del HTML ........ 1 s envio del correo ........... 2 s
Dentro del bloque de 188 segundos había un while IFS=, read con un cut y un date -d por línea (un millón de procesos) más cuatro grep -c posteriores sobre el fichero completo. Los tres cambios aplicados, en este orden y midiendo cada uno:
| Cambio | real |
Acumulado |
|---|---|---|
| Línea base | 3 min 12 s | — |
Quitar cut/date del bucle (IFS=, + printf -v) |
41 s | ×4,7 |
Sustituir bucle y los cuatro grep -c por un awk de una pasada |
4,8 s | ×40 |
LC_ALL=C en el sort del top de repartidores |
3,9 s | ×49 |
De 192 segundos a 3,9: cuarenta y nueve veces más rápido sin cambiar ni una línea de la lógica de negocio, solo dónde se ejecuta cada cosa. Y aquí se aplica el paso 4 del método: paramos. Quedan 3,9 segundos, de los cuales 2 son el envío del correo, que depende de la red. Seguir optimizando un script que se ejecuta una vez al día a las 06:30 no le aporta nada a nadie.
Errores Comunes y Consejos
- Optimizar sin línea base. Sin el número de partida no puedes demostrar la mejora ni detectar que has empeorado las cosas. Anota el
timeinicial antes de tocar nada. - Medir una sola vez. La primera ejecución paga la caché de disco fría. Ejecuta tres veces y usa la mediana; para comparar en serio,
hyperfineautomatiza justo esto. - Micro-optimizar fuera de los bucles. Cambiar
basenamepor${f##*/}en una línea que se ejecuta una vez no ahorra nada y puede restar claridad. El interior de los bucles es donde se gana. - Confundir «menos líneas» con «más rápido». Una tubería de seis etapas es corta de escribir y crea seis procesos; un
awkde tres líneas crea uno. - Paralelizar un trabajo limitado por disco. Si
reales alto pero la CPU está ociosa, más procesos solo añaden contención. - Consejo:
set -xconPS4sirve de perfilador pobre. ConPS4='+ $(date +%s.%N) 'cada línea de traza (05-03) queda marcada con su instante y ves los saltos de tiempo. - Consejo: la optimización más rentable suele ser no hacer el trabajo. Antes de acelerar el procesado de todo el histórico, pregúntate si hace falta procesar más que el día en curso.
Ejercicios
Ejercicio 1. Este bloque tarda 2 minutos con 300.000 líneas. Identifica los tres problemas de rendimiento y reescríbelo en una sola pasada.
total=0; suma=0
for id in $(cut -d, -f1 envios.csv | tail -n +2); do
linea=$(grep "^$id," envios.csv)
importe=$(echo "$linea" | cut -d, -f6)
total=$(expr $total + 1)
suma=$(echo "$suma + $importe" | bc)
done
echo "$total envios, $suma euros"Ejercicio 2. Escribe una función veloz_mide que ejecute una orden tres veces, muestre el tiempo real de cada ejecución en milisegundos y la mediana, usando date +%s%N.
Ejercicio 3. Un script tarda real 5m10s, user 0m12s, sys 0m8s. ¿Dónde está el tiempo y qué optimización tiene sentido? Y si fuera real 5m10s, user 2m40s, sys 2m20s, ¿qué buscarías?
Soluciones
Solución 1.
read -r total suma < <(awk -F, 'NR > 1 { total++; suma += $6 }
END { printf "%d %.2f\n", total, suma }' envios.csv)
printf '%d envios, %.2f euros\n' "$total" "$suma"Los tres problemas. (1) Complejidad cuadrática: por cada uno de los 300.000 identificadores se hace un grep que recorre el fichero entero; son 9×10¹⁰ comparaciones de línea. Es el defecto grave, y desaparece al recorrer el fichero una vez en lugar de buscar dentro de él. (2) Procesos dentro del bucle: grep, echo, cut, expr y bc son cinco procesos por vuelta, un millón y medio en total. (3) for sobre una sustitución de comandos, que además de crear la subshell construye en memoria una cadena con los 300.000 identificadores y la parte por espacios. La versión de awk hace una pasada, un proceso y suma en coma flotante de forma nativa, sin bc (06-01).
Solución 2.
# veloz_mide <orden...>
# Ejecuta la orden 3 veces descartando su salida y muestra los tiempos y la mediana.
veloz_mide() {
local -a tiempos=(); local i inicio
for i in 1 2 3; do
inicio=$(date +%s%N)
"$@" >/dev/null 2>&1
tiempos+=( $(( ($(date +%s%N) - inicio) / 1000000 )) )
printf ' ejecucion %d: %d ms\n' "$i" "${tiempos[-1]}"
done
mapfile -t ordenados < <(printf '%s\n' "${tiempos[@]}" | sort -n)
printf 'mediana: %d ms\n' "${ordenados[1]}"
}Se usa "$@" sin comillas de más para que la orden y sus argumentos lleguen intactos (03-05), la salida se descarta para no medir el terminal, y la mediana de tres es el elemento central del array ordenado, que es más robusta que la media ante una ejecución anómala.
Solución 3. En el primer caso el proceso solo consume 20 segundos de CPU de los 310 de reloj: está esperando, casi seguro red o disco. Optimizar el código no cambiará nada; hay que buscar la espera (un curl sin --max-time, un sleep heredado, un ssh sin ControlMaster, una lectura sobre un montaje de red) y atacarla con paralelismo, caché o límites de tiempo. En el segundo caso la CPU sí está ocupada, y el reparto casi a partes iguales entre user y sys delata creación masiva de procesos: hay que buscar órdenes externas dentro de bucles y convertirlas en expansiones o en un awk de una pasada.
Conclusión
Optimizar en Bash empieza y termina en el mismo sitio: medir. time da las tres cifras que orientan todo el trabajo —real es lo que sufre el usuario, user es cálculo y sys es núcleo, así que un sys alto significa procesos y un real muy superior a user + sys significa espera—; SECONDS acota bloques largos dentro del script y date +%s%N bloques cortos, siempre repitiendo la medición y quedándose con la mediana sobre datos representativos. El coste dominante es crear procesos: 1-3 ms cada uno, insignificantes sueltos y demoledores multiplicados por medio millón de líneas, como demuestra el salto de 21 minutos a 12 segundos con solo sacar echo y cut de dentro del bucle, y de ahí a 0,9 s con un único awk. De ahí se derivan las reglas prácticas: usa builtins en el interior de los bucles ($(( )) en vez de expr, ${f##*/} en vez de basename, ${#s} en vez de wc -c) y no te molestes fuera; elimina las etapas inútiles de las tuberías (grep -c en vez de grep | wc -l, awk que filtra en vez de grep | awk); lee el fichero una sola vez calculando todos los agregados en una pasada; evita subshells innecesarias, agrupando con { } en vez de ( ) y usando done < <(cmd); y redirige el bucle entero en lugar de acumular >>. Entre while read, mapfile y awk, la regla es que si dentro del bucle solo filtras, cuentas o sumas, ese bucle debería ser un awk; mapfile solo para ficheros pequeños que hay que recorrer varias veces. Paralelizar con xargs -P o &/wait es la mayor ganancia cuando el trabajo es independiente y pesado, pero no ayuda si el cuello de botella es el disco. LC_ALL=C multiplica por cuatro la velocidad de sort cuando el orden byte a byte sirve, y ningún log de un gigabyte debe entrar en un array. El caso real cierra el argumento: informe-diario.sh pasó de 3 min 12 s a 3,9 s —cuarenta y nueve veces— sin tocar la lógica de negocio, y entonces paramos, porque optimizar más un script que corre una vez al día no le sirve a nadie.
El toolkit ya es legible y rápido. Queda la pregunta más incómoda de las tres que abrían el módulo: ¿es seguro? Estos scripts ejecutan tareas privilegiadas, guardan claves SSH, leen ficheros de configuración con credenciales y procesan datos personales de clientes. La lección 08-03 los audita de arriba abajo: inyección de comandos y por qué eval es una bomba, validación de entrada con lista blanca, gestión de secretos, ficheros temporales seguros, mínimo privilegio, condiciones de carrera y tratamiento de datos personales, con una lista de comprobación aplicada al toolkit completo.
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
