1. El punto de llegada
En el tema 2, el primer script fueron dos líneas que preguntaban cosas y las mostraban:
#!/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:
while IFS= read -r -d '' archivo; do
ejecutar gzip -9 -- "$archivo"
comprimidos=$(( comprimidos + 1 ))
done < <(find "$dir" -type f -name '*.log' -mtime +0 -print0)
| Pieza | Lo que ahora sabes que hace, y por qué está | Tema |
|---|---|---|
find … -print0 | Elige 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 |
ejecutar | Una 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ñana | Lo 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:
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:
| Bloque | La pregunta | La 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.
| Idea | Lo que explica | Tema |
|---|---|---|
| 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ón | Dó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 mensaje | Lo que significa | Tema |
|---|---|---|
$'\r': command not found | El 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 directory | El programa de la primera línea no existe, o la línea termina con un \r invisible. | 2 |
Permission denied al ejecutar ./script | Falta el permiso de ejecución: chmod +x, o bash script. | 2 |
unbound variable | Con 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 arguments | Una variable sin comillas dentro de [ ] se partió en varias palabras, o desapareció por estar vacía. | 11 |
integer expression expected | Se comparó con -eq o -lt algo que no es un número entero. | 11 |
bad substitution | Una expansión mal escrita, o una de Bash ejecutada por sh. | 6 |
syntax error near unexpected token | Falta 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 long | Un 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 nada | Con 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 cron | El PATH de cron no incluye el directorio del programa. | 34 |
No MTA installed, discarding output | La 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 tiempo | Un comando del cuerpo, como ssh, leyó el resto de la entrada. | 16 |
| Un contador que vale cero después del bucle | El bucle estaba al final de una tubería, en una subshell. | 16 |
| Ningún mensaje, y código 0 | Funcionó. 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… | Herramientas | Temas |
|---|---|---|
| Empezar un script | #!/usr/bin/env bash, chmod +x, set -euo pipefail | 2, 4, 23 |
| Guardar y transformar datos | Variables, ${var#…}, ${var/…/…}, ${var:-…}, $(( )), arrays | 5 a 8 |
| Preguntar al usuario | read -r, read -p, read -s | 9 |
| Decidir | if, [[ ]], (( )), &&, ||, case | 10 a 13 |
| Repetir | for, while, until, while IFS= read -r, break, continue | 14 a 17 |
| Organizar | Funciones, local, return, bibliotecas con source | 18, 19, 22 |
| Recibir argumentos y opciones | $1, "$@", shift, getopts | 20, 21 |
| Resistir los fallos | set -e, trap, mktemp, flock, timeout | 15, 23, 24, 27 |
| Informar y depurar | >&2, logger, bash -x, PS4, ShellCheck | 25, 26 |
| Procesar texto | =~, BASH_REMATCH, grep, sed, awk, printf, envsubst | 28 a 31 |
| Procesar archivos en lote | find, -print0, xargs -0 -P, mv -n | 32 |
| Programar en el tiempo | crontab, systemd timers, systemd-run, at | 33 a 35 |
| Compilar y empaquetar | cc, make, -nt, tar --sort --mtime, sha256sum | 36 |
| Respaldar y verificar | tar --listed-incremental, rsync --link-dest, sha256sum -c, shuf | 37, 38 |
| Desplegar | ssh, printf '%q', bash -s, rsync, ln -s, mv -T | 39 |
| Vigilar y avisar | df, systemctl is-active, curl -w, openssl, webhooks | 40 |
| Hacer una herramienta | Subcomandos, main, install, complete, pruebas | 41 |
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:
| Herramienta | Hace | Tema |
|---|---|---|
| Una biblioteca de funciones | Mensajes, registro y validaciones para reutilizar en cualquier script. | 22, 25 |
renombrar-por-fecha | Renombra fotos por su fecha, sin perder ninguna. | 32 |
cron-tarea, timer-tarea | Agregan y quitan tareas programadas sin duplicarlas. | 33, 35 |
ejecutar-tarea | Envuelve cualquier tarea programada: entorno fijo, candado, límite de tiempo, registro y aviso solo si falla. | 34 |
build.sh | Compila solo lo que cambió, prueba y empaqueta de forma reproducible. | 36 |
respaldar, verificar-respaldo | Instantáneas diarias con rotación, y la prueba de que se pueden restaurar. | 37, 38 |
desplegar | Instala versiones por SSH, con cambio atómico y vuelta atrás. | 39 |
vigilar, avisar-webhook | Monitorea y avisa solo los cambios de estado. | 40 |
mant, probar-mant | El 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ó afuera | Lo que vimos de eso | Dónde seguir |
|---|---|---|
La portabilidad a sh | La 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 script | El 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 pruebas | El probar-mant casero del tema 41. | Herramientas como Bats, para proyectos con muchas pruebas. |
| La integración continua | Scripts 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ón | Idempotencia, estado deseado, despliegues por SSH. | Ansible y herramientas parecidas, que aplican las mismas ideas a cientos de servidores. |
| Los programas interactivos | read y la detección de una terminal. | dialog o whiptail para menús, y expect para automatizar programas que preguntan. |
| Reaccionar a eventos | Tareas 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 Bash | El 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
| Curso | Se ocupa de | Lo tocamos en |
|---|---|---|
| 1. Fundamentos de Sistemas Operativos | Qué 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 Linux | Los comandos, las tuberías y los permisos que este curso dio por sabidos. | Todo el curso |
| 4. Programación en C para Sistemas Operativos | El lenguaje del kernel y de los comandos que los scripts usan. | Temas 10, 36 y 41 |
Nivel intermedio
| Curso | Profundiza en | Lo tocamos en |
|---|---|---|
| 5. Procesos e Hilos | Có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 CPU | Có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ón | Las carreras entre procesos, y los candados que las evitan. | Temas 27, 33 y 39 |
| 8. Gestión de Memoria | La memoria virtual, la caché y los límites de memoria de un servicio. | Temas 35 y 40 |
| 9. Sistemas de Archivos y Entrada/Salida | Inodos, enlaces duros, renombrados atómicos y la corrupción de los datos. | Temas 24, 37, 38 y 39 |
| 10. Comunicación entre Procesos | Las tuberías, los descriptores y los bloqueos de archivos por dentro. | Temas 16, 19 y 27 |
Nivel avanzado
| Curso | Profundiza en | Lo tocamos en |
|---|---|---|
| 11. Redes desde el Sistema Operativo | Conexiones, puertos, TLS y nombres, vistos desde el kernel. | Temas 39 y 40 |
| 12. Seguridad en Sistemas Operativos | Privilegios, secretos, claves y la entrada que no hay que creer. | Temas 30, 37, 39 y 40 |
| 13. Virtualización y Contenedores | Máquinas dentro de máquinas, y entornos aislados y reproducibles. | Temas 34, 36 y 39 |
| 14. Rendimiento, Depuración y Observabilidad | Medir, 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 Cero | Escribir 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 Bash | Allí 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:
- Automatizar una tarea real por semana. Algo que hoy haces a mano, aunque sea pequeño: ordenar las descargas, preparar un informe, renombrar archivos. El tema 1 enseñaba a hacer la cuenta de cuánto tiempo ahorra.
- Pasar ShellCheck por todo. Por los scripts propios y, como ejercicio, por scripts ajenos. Cada advertencia que no entiendas es un tema para repasar.
- Leer scripts de otros. Tu sistema tiene decenas:
file /usr/bin/* | grep -c 'shell script'los cuenta. Los de los paquetes de tu distribución y los de proyectos conocidos enseñan estilos y trucos que ningún curso cubre. - Mantener tus herramientas en git. Las del punto 7, con sus pruebas y una historia de cambios. Es la costumbre que convierte scripts sueltos en herramientas.
- Terminar los ejercicios pendientes. Los de los bloques 6 y 9, y los del tema 41, son los que más entrenan.
- Administrar una máquina de verdad. Un servidor pequeño, una máquina virtual o una placa en casa, con un servicio andando, respaldado, vigilado y mantenido por tus propios scripts. Pone en uso todo el bloque 9.
Para quien quiera textos de consulta más amplios:
| Texto | Autor | Sirve para |
|---|---|---|
| Bash Reference Manual | Proyecto GNU | La referencia oficial, gratuita en línea. Es lo mismo que man bash, mejor organizado. |
| BashFAQ y BashPitfalls | Wiki de Greg Wooledge | Respuestas precisas a las preguntas más frecuentes, y una lista de los errores más comunes, con su explicación. |
| Learning the bash Shell | Cameron Newham | Un recorrido completo por el lenguaje, más extenso que este curso. |
| Classic Shell Scripting | Arnold Robbins y Nelson Beebe | Scripts portables y el arte de combinar las herramientas de Unix. |
| The Linux Command Line | William Shotts | Su 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
- Un script es un archivo de comandos con una primera línea que dice quién lo interpreta, y se convierte en un comando más.
- Bash transforma cada línea en etapas antes de ejecutarla; las comillas deciden qué se expande, y casi siempre van.
- Las variables guardan texto; las expansiones lo transforman,
(( ))calcula con enteros, y los arrays guardan listas y tablas. - Todo comando termina con un código, y sobre ese código deciden
if,while,&&y||. - Las funciones devuelven un código y escriben un dato; los argumentos y las opciones se validan antes de usarlos.
- El modo estricto detiene el script en el primer error,
traplimpia al salir, y los mensajes van a la salida de errores y a un registro. - Un buen script se puede repetir sin daño, y no deja que dos ejecuciones se pisen.
- Los datos que vienen de afuera se validan y nunca se ejecutan; los nombres de archivo viajan separados por nulos.
- Una tarea programada corre en un entorno mínimo, sin terminal y sin nadie mirando: rutas absolutas, salida guardada, aviso si falla.
- Lo que se escribe se escribe completo antes de existir: nombre provisional y
mv. - Compilar, respaldar, verificar, desplegar y vigilar son, en el fondo, los mismos patrones aplicados a problemas distintos.
- Una herramienta de verdad se diseña por su interfaz, se organiza en funciones, simula antes de cambiar y se prueba antes de confiar en ella.
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
- Vuelve al tema 2 y reescribe
hola.shcon todo lo aprendido: modo estricto, una funciónmain, una opción-n NOMBREpara 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? - Elige las tres ideas de la sección 3 que te resulten menos claras, y repasa los temas indicados.
- Recorre la tabla de errores de la sección 5 y provoca cada uno a propósito en un script de prueba.
- Pasa ShellCheck por todos los scripts que escribiste durante el curso, y corrige lo que señale.
- 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.