← Automatización con Scripts en Bash Bloque 10 · Integración y cierre · Tema 42 de 42

Tema 42

Cierre y puente al próximo curso

El curso empezó con dos líneas que saludaban y decían la fecha. Cuarenta y un temas después, este último no enseña ninguna construcción nueva. Ordena lo aprendido, separa las ideas que conviene llevarse de los detalles que se pueden volver a buscar, explica los mensajes de error que al principio desconcertaban, reconoce lo que quedó sin ver y muestra por dónde sigue el camino: un nivel más abajo, en el lenguaje en que está escrito el propio sistema operativo.

1. El punto de llegada

En el tema 2, el primer script fueron dos líneas que preguntaban cosas y las mostraban:

hola.sh, en el tema 2
#!/usr/bin/env bash
# hola.sh — mi primer script: saluda y dice dónde se ejecuta
echo "Hola, $USER. Hoy es $(date +%A)."
echo "Estás en $(pwd), en el equipo $(hostname)."

Estas son cuatro líneas de mant, la herramienta del tema 41. Al comienzo del curso habrían sido un jeroglífico; ahora cada pieza tiene una razón:

cuatro líneas del tema 41
while IFS= read -r -d '' archivo; do
  ejecutar gzip -9 -- "$archivo"
  comprimidos=$(( comprimidos + 1 ))
done < <(find "$dir" -type f -name '*.log' -mtime +0 -print0)
PiezaLo que ahora sabes que hace, y por qué estáTema
find … -print0Elige los archivos por su antigüedad, y separa los nombres con el carácter nulo, el único que no puede aparecer en un nombre.32
< <( … )La sustitución de procesos convierte la salida de find en un archivo que el bucle lee, sin una tubería que lo encierre en una subshell, de modo que el contador sobrevive al bucle.16
IFS= read -r -d ''Lee hasta el próximo nulo, sin recortar espacios y sin interpretar barras invertidas: el nombre llega intacto, sea cual sea.16, 32
"$archivo"Las comillas impiden que la división en palabras y los comodines toquen el valor.3, 5
--Marca el final de las opciones: un archivo llamado -raro.log no se confunde con una opción de gzip.32
ejecutarUna función propia que, con -n, muestra en lugar de hacer, y que anota el fallo si lo hay.18, 41
$(( … )) y no (( c++ ))La aritmética de Bash, escrita de modo que nunca devuelva un código de error cuando el resultado es cero, por el modo estricto.7, 23
Que funcione a las 3 de la mañanaLo lanza un temporizador de systemd, con un entorno mínimo, y deja todo en un registro.34, 35

Entre un script y el otro no hay una lista de construcciones memorizadas: hay un modelo. Sabes cómo lee Bash una línea antes de ejecutarla, qué es un código de salida y por qué el cero es éxito, qué ve una función y qué ve un proceso hijo, en qué entorno corre una tarea programada y qué hacer para que un error no pase en silencio. Con eso, un script ajeno deja de ser una caja negra, y uno propio deja de ser una apuesta.

Las habilidades del curso se apilan, cada una sobre las anteriores:

Las habilidades del curso, apiladas de abajo hacia arriba, con los temas que trataron cada una: el lenguaje, temas 1 a 22; la robustez, temas 23 a 27; el texto, temas 28 a 32; el tiempo, temas 33 a 35; la práctica, temas 36 a 40; e integrar, tema 41 Integrar · una herramienta completa · tema 41 Practicar · compilar, respaldar, desplegar, vigilar · temas 36 a 40 Programar en el tiempo · cron, systemd y at · temas 33 a 35 Procesar texto · regex, grep, sed, awk, lotes · temas 28 a 32 Resistir · modo estricto, trap, registros, idempotencia · 23 a 27 El lenguaje · datos, condiciones, bucles, funciones · temas 1 a 22
Cada capa se apoya en las de abajo. mant, en la cima, usa todas a la vez.

2. El curso en diez respuestas

Cada bloque respondió una pregunta. Estas son las respuestas, en una frase cada una:

BloqueLa preguntaLa respuesta
1. Del comando al script
Temas 1 a 4
¿Qué es un script, y cómo lo lee Bash?Un archivo de comandos con una primera línea que dice quién lo interpreta; Bash lee cada línea en etapas fijas, expandiendo antes de ejecutar, y las comillas deciden qué se expande.
2. Variables y datos
Temas 5 a 9
¿Cómo guardo y transformo datos?En variables que son siempre texto, con expansiones que cortan y reemplazan sin llamar a otros programas, aritmética entera, arrays y lo que el usuario escribe con read.
3. Códigos de salida y condicionales
Temas 10 a 13
¿Cómo decide un script?Por el código de salida de un comando: if no evalúa una expresión, ejecuta un comando, y [[ ]], (( )) y case son comandos hechos para preguntar.
4. Bucles
Temas 14 a 17
¿Cómo repite?Sobre listas con for, mientras algo sea cierto con while, y sobre las líneas de un archivo con while IFS= read -r, cuidando quién se come la entrada.
5. Funciones y argumentos
Temas 18 a 22
¿Cómo lo organizo, y cómo recibe datos?En funciones que devuelven un código y escriben un resultado, con variables locales; y con argumentos y opciones al estilo Unix, procesados con getopts.
6. Manejo de errores y scripts robustos
Temas 23 a 27
¿Cómo hago que no falle en silencio?Con el modo estricto, trampas que limpian al salir, mensajes a la salida de errores y registros, una traza para depurar, y operaciones que se pueden repetir sin daño.
7. Texto y expresiones regulares
Temas 28 a 32
¿Cómo proceso datos y archivos?Con expresiones regulares para validar, grep, sed y awk para transformar, lectura segura de CSV y configuración, plantillas para informes, y find con separadores nulos para los lotes.
8. Tareas programadas
Temas 33 a 35
¿Cómo corre sin mí?Con cron o con temporizadores de systemd, sabiendo que la tarea corre en un entorno mínimo, sin terminal, y que su salida hay que guardarla en algún lado.
9. Automatización en la práctica
Temas 36 a 40
¿Para qué sirve todo esto?Para compilar solo lo que cambió, respaldar con historia y verificar que se puede restaurar, desplegar sin cortes y con vuelta atrás, y vigilar avisando solo cuando hace falta.
10. Integración y cierre
Temas 41 y 42
¿Cómo se construye una herramienta de verdad?Diseñando primero la interfaz, organizando el código en funciones con un main, centralizando lo que cambia el sistema, y probándolo antes de confiar en él.

3. Las ideas que sostienen todo

La sintaxis se olvida, y se vuelve a buscar. Estas diez ideas no se olvidan, porque explican por qué Bash es como es y por qué los buenos scripts se parecen entre sí. Si alguna no resulta evidente, el tema indicado es el que conviene repasar.

IdeaLo que explicaTema
Bash transforma la línea antes de ejecutarla. Expansiones, división en palabras, comodines, y recién después el comando.Por qué una variable sin comillas se parte en pedazos, por qué [ $x = y ] falla si x está vacía, y por qué casi siempre la respuesta es «poner comillas».3
Todo es texto. Las variables guardan cadenas; los números son cadenas que (( )) interpreta.Por qué no hay decimales, por qué == y -eq son distintos, y por qué los datos estructurados se llevan mejor con otras herramientas.5, 7
El código de salida es el verdadero o falso. Cero es éxito, y toda decisión se toma sobre el código de un comando.if, &&, ||, while, el valor de una función, y por qué [[ es un comando.10, 11
Una función devuelve dos cosas. Un código, que dice si funcionó, y una salida, que es el dato.return frente a echo, por qué los mensajes van a la salida de errores, y lo que se pierde dentro de $( ).19, 25
Un hijo recibe una copia, y no devuelve nada. Las subshells, las tuberías y los scripts trabajan sobre copias.El contador que vuelve a cero después de una tubería, por qué un script no cambia el directorio de la terminal, y la diferencia entre ejecutar y source.2, 16
Fallar pronto y en voz alta. Un error detectado en la línea donde ocurre es barato; el mismo error tres pasos después, carísimo.El modo estricto, validar los argumentos al comienzo, los mensajes que dicen qué y dónde, los códigos de salida distintos para cada caso.23, 25
Salir siempre deja todo en orden. Termine como termine, el script limpia lo que creó.trap … EXIT, los temporales con mktemp, la escritura atómica con un nombre provisional y mv.24
Un buen script se puede repetir. Describe un estado deseado, y llegar a él dos veces es lo mismo que una.La idempotencia, los candados con flock, las herramientas que comprueban antes de actuar, como cron-tarea, timer-tarea o respaldar.27
Los datos nunca se ejecutan. Lo que viene de afuera —un archivo, un argumento, un nombre— se valida y se usa como dato, nunca como código.Por qué no source para la configuración, ni eval, ni armar un programa de awk o sed con variables adentro; printf '%q' para la shell remota; listas cerradas antes de "cmd_$x".29, 30, 39
El script no corre en tu terminal. Corre en cron, en systemd o en otra máquina, sin tu PATH, tu idioma ni tus ojos.Las rutas absolutas, el PATH fijo, el silencio cuando todo va bien, los registros, los avisos y el latido.34, 40

Las seis últimas son la diferencia entre un script que funciona y uno en el que se puede confiar. Ninguna depende de Bash: valen para cualquier programa que vaya a ejecutarse sin que nadie lo mire, y las volverás a encontrar en C, en los servicios del sistema y en cualquier lenguaje que uses después.

4. Los patrones que se repiten

A lo largo del curso, las mismas formas aparecieron en scripts muy distintos. Reconocerlas es lo que permite escribir uno nuevo sin empezar de cero, y leer uno ajeno sin perderse.

PatrónDónde apareció
El encabezado de siempre.#!/usr/bin/env bash, un comentario con el uso, set -euo pipefail, y las funciones uso y error: en todos los scripts completos desde el bloque 6.
Validar todo antes de actuar.getopts, la cantidad de argumentos, las expresiones regulares sobre cada dato, la configuración entera antes del primer control: vigilar, mant, desplegar.
Capturar el código sin morir.comando || codigo=$? y un case sobre el código: los avisos de tar y rsync, el 124 de timeout, los estados de vigilar.
Nulos de punta a punta.find -print0, xargs -0, sort -z, read -d '': los lotes del tema 32, los manifiestos del 38, las rotaciones del 41.
Nombre provisional y mv.El archivo que se escribe completo antes de existir: la escritura atómica del tema 24, la instantánea .incompleta del 37, el manifiesto .tmp del 38, el current.nuevo del 39.
Escribir solo si cambia.Comparar el contenido nuevo con el actual y no tocar nada si son iguales: cron-tarea, timer-tarea, el build/config del tema 36, mant programar.
Simular primero.-n en renombrar-por-fecha, respaldar y mant; rsync -n; systemd-analyze calendar; un único punto, ejecutar, que respeta la simulación.
Un candado.flock -n sobre un descriptor, o mkdir en el servidor: ejecutar-tarea, respaldar, desplegar, mant.
Subcomandos con una lista cerrada.Un case que valida, y una función por acción: cron-tarea poner, build.sh probar, desplegar volver, mant limpiar.
Calcular primero, presentar después.Reunir los datos en variables y arrays, y recién al final darles forma: los informes del tema 31, la rotación del 37, el informe de verificar-respaldo.
Silencio cuando todo va bien.Escribir solo cuando hay algo que hacer: ejecutar-tarea, chronic, vigilar que avisa solo los cambios, mant -q.

5. Los errores, ahora con explicación

Los mensajes de error de Bash son breves y, al principio, crípticos. Pero casi siempre dicen exactamente qué pasó y en qué línea. Al comienzo del curso eran frases en inglés; ahora cada una remite a un mecanismo conocido.

El mensajeLo que significaTema
$'\r': command not foundEl script se guardó con finales de línea de Windows, y Bash ve un retorno de carro al final de cada línea.2
bad interpreter: No such file or directoryEl programa de la primera línea no existe, o la línea termina con un \r invisible.2
Permission denied al ejecutar ./scriptFalta el permiso de ejecución: chmod +x, o bash script.2
unbound variableCon set -u, se usó una variable que nunca recibió valor; a menudo, un argumento que no se pasó. ${1:-} lo hace opcional a propósito.23
binary operator expected, too many argumentsUna variable sin comillas dentro de [ ] se partió en varias palabras, o desapareció por estar vacía.11
integer expression expectedSe comparó con -eq o -lt algo que no es un número entero.11
bad substitutionUna expansión mal escrita, o una de Bash ejecutada por sh.6
syntax error near unexpected tokenFalta o sobra algo en la estructura: un fi, un done, un ; antes de then. El error suele estar antes de la línea indicada.26
Argument list too longUn comodín produjo más argumentos de los que caben en una línea de comandos: find con -exec … + o xargs.32
El script termina sin decir nadaCon set -e, un comando falló en un lugar inesperado, o una aritmética dio cero. bash -x o un trap … ERR muestran dónde.23, 26
/bin/sh: 1: programa: not found en cronEl PATH de cron no incluye el directorio del programa.34
No MTA installed, discarding outputLa tarea escribió algo, quizás un error, y nadie lo verá: hay que redirigir la salida.34
Un bucle while read que termina antes de tiempoUn comando del cuerpo, como ssh, leyó el resto de la entrada.16
Un contador que vale cero después del bucleEl bucle estaba al final de una tubería, en una subshell.16
Ningún mensaje, y código 0Funcionó. O el script es de los que callan los errores: por eso los demás temas.10

La costumbre que resume la tabla es la del tema 26: leer el mensaje completo, con su número de línea, antes de cambiar nada. Y antes incluso de ejecutar, pasar el script por ShellCheck, que encuentra la mitad de estos errores sin correr una sola línea.

6. La caja de herramientas

El inventario del curso, ordenado por lo que se quiere hacer. No es para memorizar: es para saber que la herramienta existe, y dónde está explicada.

Para…HerramientasTemas
Empezar un script#!/usr/bin/env bash, chmod +x, set -euo pipefail2, 4, 23
Guardar y transformar datosVariables, ${var#…}, ${var/…/…}, ${var:-…}, $(( )), arrays5 a 8
Preguntar al usuarioread -r, read -p, read -s9
Decidirif, [[ ]], (( )), &&, ||, case10 a 13
Repetirfor, while, until, while IFS= read -r, break, continue14 a 17
OrganizarFunciones, local, return, bibliotecas con source18, 19, 22
Recibir argumentos y opciones$1, "$@", shift, getopts20, 21
Resistir los fallosset -e, trap, mktemp, flock, timeout15, 23, 24, 27
Informar y depurar>&2, logger, bash -x, PS4, ShellCheck25, 26
Procesar texto=~, BASH_REMATCH, grep, sed, awk, printf, envsubst28 a 31
Procesar archivos en lotefind, -print0, xargs -0 -P, mv -n32
Programar en el tiempocrontab, systemd timers, systemd-run, at33 a 35
Compilar y empaquetarcc, make, -nt, tar --sort --mtime, sha256sum36
Respaldar y verificartar --listed-incremental, rsync --link-dest, sha256sum -c, shuf37, 38
Desplegarssh, printf '%q', bash -s, rsync, ln -s, mv -T39
Vigilar y avisardf, systemctl is-active, curl -w, openssl, webhooks40
Hacer una herramientaSubcomandos, main, install, complete, pruebas41

7. Lo que quedó en tu equipo

Si hiciste los ejemplos, tienes ahora una colección de herramientas que siguen siendo útiles, y que forman entre todas un pequeño sistema de automatización:

HerramientaHaceTema
Una biblioteca de funcionesMensajes, registro y validaciones para reutilizar en cualquier script.22, 25
renombrar-por-fechaRenombra fotos por su fecha, sin perder ninguna.32
cron-tarea, timer-tareaAgregan y quitan tareas programadas sin duplicarlas.33, 35
ejecutar-tareaEnvuelve cualquier tarea programada: entorno fijo, candado, límite de tiempo, registro y aviso solo si falla.34
build.shCompila solo lo que cambió, prueba y empaqueta de forma reproducible.36
respaldar, verificar-respaldoInstantáneas diarias con rotación, y la prueba de que se pueden restaurar.37, 38
desplegarInstala versiones por SSH, con cambio atómico y vuelta atrás.39
vigilar, avisar-webhookMonitorea y avisa solo los cambios de estado.40
mant, probar-mantEl mantenimiento rutinario de un servidor, con sus pruebas.41

Juntas resuelven buena parte de lo que necesita un servidor pequeño: respaldar cada noche, seguido de verificar-respaldo manifiesto; vigilar cada cinco minutos; mant todo cada madrugada; desplegar cuando hay una versión nueva. Conviene guardarlas en un repositorio de git, con su historia, y respaldarlas con la primera de ellas.

8. Lo que quedó sin ver

Un curso elige. Varios temas se nombraron y se dejaron a un lado, a propósito, para no perder el hilo. Conviene saber cuáles son:

Quedó afueraLo que vimos de esoDónde seguir
La portabilidad a shLa diferencia entre Bash y sh, y por qué dash no entiende [[ ]].El estándar POSIX, para scripts que deban correr en cualquier Unix o en contenedores mínimos.
Los procesos en segundo plano dentro de un scriptEl trabajo en paralelo con xargs -P, del tema 32.& y wait dentro de un script, los coprocesos y el control de trabajos; y el curso Procesos e Hilos.
Los marcos de pruebasEl probar-mant casero del tema 41.Herramientas como Bats, para proyectos con muchas pruebas.
La integración continuaScripts que un sistema podría ejecutar en cada cambio, como build.sh.GitHub Actions, GitLab CI y similares, que ejecutan exactamente esos scripts.
La gestión de configuraciónIdempotencia, estado deseado, despliegues por SSH.Ansible y herramientas parecidas, que aplican las mismas ideas a cientos de servidores.
Los programas interactivosread y la detección de una terminal.dialog o whiptail para menús, y expect para automatizar programas que preguntan.
Reaccionar a eventosTareas que corren a una hora, o cada tantos minutos.inotifywait para actuar cuando cambia un archivo, y las unidades .path de systemd.
Cuándo dejar BashEl tema 1 lo planteó: datos estructurados, cálculos, programas largos.Python, para lo que pide estructuras de datos; C, para lo que pide hablar con el sistema directamente.

9. La ruta: doce cursos más

Este curso es el tercero de una serie de quince. Enseñó a programar el sistema desde afuera: dándole órdenes a través de los programas que ya trae. Los que siguen bajan, curso a curso, hacia lo que ocurre por dentro, y casi todos retoman algo que aquí se usó sin explicar cómo funciona.

Nivel inicial

CursoSe ocupa deLo tocamos en
1. Fundamentos de Sistemas OperativosQué hace el kernel, y qué ocurre entre un programa y la máquina.Temas 2, 10 y 24
2. Terminal y Línea de Comandos en LinuxLos comandos, las tuberías y los permisos que este curso dio por sabidos.Todo el curso
4. Programación en C para Sistemas OperativosEl lenguaje del kernel y de los comandos que los scripts usan.Temas 10, 36 y 41

Nivel intermedio

CursoProfundiza enLo tocamos en
5. Procesos e HilosCómo se crea un proceso, qué recibe un hijo, qué es una señal por dentro.Temas 2, 24 y 32
6. Planificación de la CPUCómo decide el kernel quién usa el procesador, y qué significan la prioridad y la carga.Temas 32, 35 y 40
7. Concurrencia y SincronizaciónLas carreras entre procesos, y los candados que las evitan.Temas 27, 33 y 39
8. Gestión de MemoriaLa memoria virtual, la caché y los límites de memoria de un servicio.Temas 35 y 40
9. Sistemas de Archivos y Entrada/SalidaInodos, enlaces duros, renombrados atómicos y la corrupción de los datos.Temas 24, 37, 38 y 39
10. Comunicación entre ProcesosLas tuberías, los descriptores y los bloqueos de archivos por dentro.Temas 16, 19 y 27

Nivel avanzado

CursoProfundiza enLo tocamos en
11. Redes desde el Sistema OperativoConexiones, puertos, TLS y nombres, vistos desde el kernel.Temas 39 y 40
12. Seguridad en Sistemas OperativosPrivilegios, secretos, claves y la entrada que no hay que creer.Temas 30, 37, 39 y 40
13. Virtualización y ContenedoresMáquinas dentro de máquinas, y entornos aislados y reproducibles.Temas 34, 36 y 39
14. Rendimiento, Depuración y ObservabilidadMedir, encontrar el cuello de botella y entender un sistema en producción.Temas 26, 35 y 40
15. Construcción de un Sistema Operativo desde CeroEscribir un kernel mínimo: compilarlo, enlazarlo y arrancarlo.Tema 36

Los scripts son la herramienta de trabajo de todos los cursos que siguen: compilar un programa de prueba cien veces, medir un experimento, preparar un entorno, recoger resultados. Lo que en ellos haya que repetir, ya sabes cómo automatizarlo.

10. El próximo paso

El curso que sigue es Programación en C para Sistemas Operativos. Un script le pide cosas al sistema a través de programas: para comprimir llama a gzip, para bloquear llama a flock, para renombrar llama a mv. Esos programas están escritos en C, y lo que hacen por dentro es pedírselo directamente al kernel, con llamadas al sistema. El curso siguiente baja a ese nivel. Mucho de lo que allí se estudia ya pasó por delante de tus ojos:

Lo vimos aquí, desde BashAllí se estudia, desde C
El main.c de mayus, con su fgets, su toupper y su char linea[4096], en el tema 36.El lenguaje: tipos, arreglos, cadenas terminadas en un nulo, y por qué un arreglo de tamaño fijo puede desbordarse.
cc -c, los archivos objeto, el enlazado, los .h, -MMD y make, del tema 36.El modelo de compilación de C: preprocesador, compilador, enlazador y bibliotecas.
Los códigos de salida, de 0 a 255, del tema 10.El valor que devuelve main, la función exit, y errno, que dice por qué falló una llamada.
Los descriptores: exec 9> archivo, 2>&1, < /dev/null.open, read, write, close y dup2: los números de descriptor son los mismos.
trap y las señales, del tema 24.sigaction: cómo un programa atiende una señal.
mv como operación atómica, de los temas 24 y 39.La llamada rename, que es la que garantiza esa atomicidad.
flock, del tema 27.La llamada al sistema del mismo nombre.
$(( )) con enteros de 64 bits, del tema 7.Los tipos enteros de C, sus tamaños y lo que pasa cuando se desbordan.
Las variables de entorno que un hijo hereda, del tema 5.getenv, y el entorno como un arreglo que cada proceso recibe al nacer.

La diferencia es de nivel. En Bash, un error de memoria es imposible y un archivo es un nombre; en C, la memoria se administra a mano y un archivo es un número que devolvió el kernel. Es más trabajo, y es exactamente lo que permite entender, en los cursos siguientes, cómo funcionan los procesos, la memoria y los sistemas de archivos que hasta aquí se usaron desde afuera.

Llegas con ventaja. Quien aprende C para sistemas sin saber automatizar tiene que compilar, ejecutar y comprobar cada programa a mano. Tú puedes escribir en diez minutos el build.sh que lo compile, lo pruebe con casos de entrada y salida y te diga qué falló.

11. Cómo seguir practicando

Bash se aprende como cualquier herramienta: usándola para problemas propios. Algunas maneras, de menor a mayor esfuerzo:

Para quien quiera textos de consulta más amplios:

TextoAutorSirve para
Bash Reference ManualProyecto GNULa referencia oficial, gratuita en línea. Es lo mismo que man bash, mejor organizado.
BashFAQ y BashPitfallsWiki de Greg WooledgeRespuestas precisas a las preguntas más frecuentes, y una lista de los errores más comunes, con su explicación.
Learning the bash ShellCameron NewhamUn recorrido completo por el lenguaje, más extenso que este curso.
Classic Shell ScriptingArnold Robbins y Nelson BeebeScripts portables y el arte de combinar las herramientas de Unix.
The Linux Command LineWilliam ShottsSu segunda mitad, sobre scripts, repasa este curso con otros ejemplos. Gratuito en línea.

Y antes que cualquier libro, la documentación que ya está en tu máquina: help para las órdenes internas de Bash, como help read o help [[, y man bash, que ahora puedes leer.

12. El curso, en doce frases

13. Autoevaluación final

Estas preguntas no corresponden a un tema: cada una pide relacionar varios. Si puedes responderlas sin consultar, el objetivo del curso está cumplido.

1. ¿Por qué for f in $(ls *.txt) es una mala forma de recorrer archivos, y cuál es la buena?

Porque la salida de $(ls …) pasa por la división en palabras y por la expansión de comodines: un nombre con espacios se parte en varios, y uno con un asterisco se expande. Además, ls es innecesario, porque el comodín ya produce la lista. La buena es for f in ./*.txt, con "$f" entre comillas en el cuerpo y nullglob si puede no haber ninguno; o, para árboles y criterios, find … -print0 con while IFS= read -r -d ''.

2. Un contador incrementado dentro de cat archivo | while read l; do …; done vale 0 después del bucle. ¿Por qué, y cómo se corrige?

Porque cada parte de una tubería se ejecuta en una subshell, y el bucle modificó la copia de la variable que tenía su subshell. Se corrige leyendo el archivo con una redirección, done < archivo, o la salida de un comando con una sustitución de procesos, done < <(comando), de modo que el bucle corre en la shell principal.

3. ¿Qué tres cosas hace set -euo pipefail, y en qué casos -e no detiene el script aunque un comando falle?

-e termina el script cuando un comando falla; -u, cuando se usa una variable sin valor; pipefail hace que una tubería falle si falla cualquiera de sus partes, no solo la última. -e no actúa sobre los comandos que son la condición de un if o un while, los que están antes del último de una lista con && o ||, ni los negados con !; tampoco, en general, dentro de una función llamada en uno de esos contextos.

4. Una función tiene que devolver un nombre de archivo y además indicar si lo encontró. ¿Cómo lo hace, y dónde van sus mensajes de error?

El nombre lo escribe en su salida estándar, y quien la llama lo captura con $( ); si lo encontró o no lo indica con su código de retorno, return 0 o return 1, que se usa como condición: if archivo=$(buscar); then. Los mensajes van a la salida de errores, con >&2, para no mezclarse con el dato capturado.

5. ¿Qué tiene que tener un script para que se lo pueda ejecutar dos veces seguidas sin problemas, incluso si la primera vez se interrumpió a la mitad?

Debe ser idempotente: comprobar el estado antes de actuar y describir el resultado deseado en lugar de pasos ciegos, como mkdir -p o reemplazar una línea en vez de agregarla. Debe escribir cada resultado con un nombre provisional y renombrarlo al terminar, para que nada quede a medias con el nombre definitivo. Debe limpiar con trap … EXIT lo que creó. Y debe tomar un candado, para que dos ejecuciones no corran a la vez.

6. Un script funciona en la terminal pero no en cron. Nombra cuatro causas posibles y cómo comprobarlas.

El PATH reducido no incluye un programa: se ve en el registro como not found, y se corrige con rutas absolutas o un PATH fijo. El directorio de trabajo es el personal, y una ruta relativa no se encuentra: se calcula desde la ubicación del script. Un % sin escapar cortó la línea del crontab: el registro de cron muestra el comando truncado. Falta algo de la sesión, como el agente de SSH o el idioma. Todas se reproducen con env -i … /bin/sh -c 'comando' desde el directorio personal.

7. ¿Por qué no se carga un archivo de configuración con source, y cómo se lee en su lugar?

Porque source lo ejecuta como código: cualquier línea, por error o malicia, correría con los permisos del script, a menudo los de root. Se lee línea por línea con while IFS= read -r, se separa la clave del valor con una expresión regular o con IFS, se aceptan solo claves conocidas, se valida cada valor, y se informa cualquier línea inválida con su número.

8. ¿Qué diferencia hay entre "$@", "$*" y $@ sin comillas?

"$@" se expande en un argumento por cada parámetro, exactamente como llegaron. "$*" los une todos en una sola palabra, separados por el primer carácter de IFS. $@ sin comillas los somete a la división en palabras y a los comodines, de modo que un argumento con espacios se parte. Para pasar los argumentos a otro comando, casi siempre "$@".

9. ¿Por qué ln -sfn nueva current no basta para un cambio de versión sin cortes, y qué se hace en su lugar?

Porque borra el enlace viejo y después crea el nuevo, y entre las dos operaciones current no existe. Se crea el enlace nuevo con otro nombre y se lo renombra encima del viejo con mv -T, que el kernel hace en una sola operación: quien mire ve el viejo o el nuevo, nunca un hueco. Es la misma idea que la escritura atómica de archivos.

10. ¿Cuándo no es un respaldo una copia, y qué hace falta para confiar en uno?

Cuando refleja el estado actual, como un espejo o una sincronización, porque copia también los borrados y los daños; o cuando está en el mismo disco que el original. Un respaldo conserva versiones del pasado, aparte del original. Para confiar en él hace falta comprobar que se sigue haciendo, que contiene lo importante, que sus sumas de verificación coinciden, y sobre todo que se puede restaurar, haciéndolo de verdad, de forma periódica y automática.

11. Un monitoreo corre cada cinco minutos. ¿Qué tiene que hacer para que sus avisos sean útiles, y no ruido?

Avisar solo cuando un control cambia de estado, y no en cada ejecución; avisar también cuando el problema se resuelve; repetir el aviso de un problema que sigue solo cada varias horas; usar dos umbrales, uno con margen y otro urgente; distinguir «hay un problema» de «no se pudo comprobar»; y enviar un latido a un servicio externo, para que alguien se entere si el propio monitoreo deja de funcionar.

12. ¿Por qué hay que tener cuidado al pasar datos a ssh servidor comando, a un programa de awk o a una expresión de sed, y cuál es la solución en cada caso?

Porque en los tres casos el dato se mezcla con código que otro programa interpreta: la shell remota vuelve a dividir la línea, awk ejecutaría lo que se haya insertado en su programa, y sed trataría los caracteres especiales del valor como parte de la expresión. Las soluciones son pasar el dato por separado: printf '%q' o un script a bash -s con argumentos para ssh; -v o ENVIRON para awk; y escapar el valor, o usar otra herramienta, para sed.

13. ¿Cómo hace build.sh para recompilar solo lo que cambió, y qué tres cosas tiene que considerar?

Compara fechas con -nt: un archivo generado se rehace si no existe o si alguna de sus dependencias es más nueva. Tiene que considerar la fuente de cada objeto; los encabezados que incluye, que el compilador informa en los archivos .d con -MMD; y la configuración, el compilador, las opciones y la versión, guardada en un archivo que solo se reescribe cuando cambia.

14. Al diseñar una herramienta como mant, ¿qué decides primero, y por qué?

La interfaz: los subcomandos, las opciones, el formato de la configuración, los códigos de salida y dónde viven sus archivos. Es lo que otras personas y scripts van a usar, y lo más difícil de cambiar después sin romper su trabajo. Escribir primero la ayuda es una buena forma de decidirla. El código interno se puede reescribir sin que nadie lo note.

15. Si tuvieras que explicarle a alguien, en un minuto, cuándo conviene un script de Bash y cuándo no, ¿qué dirías?

No hay una única respuesta. Una posible: Bash es el mejor lenguaje para unir programas que ya existen —copiar, comprimir, buscar, conectarse, programar en el tiempo— y para automatizar lo que se haría a mano en una terminal; un script de cien líneas reemplaza horas de trabajo repetido. Deja de convenir cuando el problema es de datos más que de programas: estructuras anidadas, cálculos con decimales, lógica que crece hasta miles de líneas. Ahí conviene Python; y para hablar directamente con el sistema operativo, C. La habilidad no es elegir uno para siempre, sino reconocer, ante cada problema, cuál le queda.

Para terminar

  1. Vuelve al tema 2 y reescribe hola.sh con todo lo aprendido: modo estricto, una función main, una opción -n NOMBRE para saludar a otra persona, su ayuda y su código de salida. ¿Cuántas líneas tiene ahora, y cuáles de ellas están para cuando algo salga mal?
  2. Elige las tres ideas de la sección 3 que te resulten menos claras, y repasa los temas indicados.
  3. Recorre la tabla de errores de la sección 5 y provoca cada uno a propósito en un script de prueba.
  4. Pasa ShellCheck por todos los scripts que escribiste durante el curso, y corrige lo que señale.
  5. Toma una tarea de tu trabajo o de tu casa que hagas todas las semanas, y conviértela en una herramienta con subcomandos, configuración, registro, simulación, pruebas y una tarea programada, siguiendo el modelo del tema 41.

Aquí termina Automatización con Scripts en Bash. Empezó con dos líneas que decían la fecha y termina con herramientas que compilan, respaldan, despliegan y vigilan solas, a cualquier hora, y avisan cuando algo no anda. Lo que cambió no es la cantidad de comandos que conoces, sino lo que puedes delegar: tareas que antes requerían que estuvieras ahí, y ahora no. El próximo curso baja un nivel, hasta el lenguaje en que están escritos los programas que tus scripts dirigen, y el sistema que los ejecuta.