Limited Time Offer: Get 50% OFF your first month of Pro & Ultra plans 🎉

Tutorial de Bash scripting: automatiza y limpia tu terminal

Sep 21, 2026

Qué puedes automatizar con Bash hoy mismo

Bash no es el lenguaje más elegante ni el más moderno, pero sigue siendo el único que está garantizado en prácticamente cualquier servidor Linux, contenedor, máquina de integración continua o portátil de desarrollo. Esa ubicuidad es su ventaja competitiva real: un script escrito una tarde sigue funcionando cuando cambias de proveedor de nube, de distribución o de equipo, porque no depende de un intérprete que haya que instalar ni de un gestor de paquetes concreto.

La segunda ventaja es que Bash habla el idioma del sistema operativo. Cuando automatizas con Bash no estás llamando a una capa intermedia: estás ejecutando exactamente los mismos comandos que ejecutarías a mano, con los mismos permisos y los mismos resultados. Para tareas de orquestación —copiar, mover, comprimir, renombrar, lanzar procesos, encadenar utilidades— eso es justo lo que necesitas.

Ejemplos concretos que puedes automatizar en una tarde:

  • Limpiar la pantalla de la terminal y dejar un estado predecible antes de una demostración, una grabación de pantalla o una sesión de depuración.
  • Preparar un entorno de desarrollo: clonar repositorios, crear entornos virtuales, instalar dependencias, copiar archivos de configuración y arrancar servicios auxiliares.
  • Renombrar y clasificar cientos de archivos con nombres generados automáticamente por herramientas de render, edición o generación de medios.
  • Lanzar copias de seguridad incrementales de una carpeta de proyecto y sincronizarlas con almacenamiento remoto.
  • Consultar un servicio web, filtrar la respuesta y generar un informe en CSV o JSON.
  • Vigilar una carpeta y disparar una acción cuando aparece un archivo nuevo, por ejemplo cuando termina una exportación.
  • Recolectar métricas simples del sistema —espacio en disco, memoria, procesos colgados— y enviarlas a un log rotativo.

La regla práctica es sencilla: si has repetido la misma secuencia de tres o más comandos dos veces esta semana, merece un script. El coste de escribirlo es bajo y el retorno se multiplica con cada ejecución.

Cuándo Bash y cuándo otro lenguaje

Antes de escribir nada, hazte tres preguntas. Primero: ¿la tarea consiste sobre todo en ejecutar comandos del sistema y mover archivos? Entonces Bash es la elección natural, porque ya tienes acceso directo a todo. Segundo: ¿necesitas estructuras de datos complejas, pruebas automatizadas, análisis de texto elaborado o decenas de llamadas encadenadas a servicios externos con lógica condicional densa? Entonces un lenguaje con tipos y bibliotecas te ahorrará dolores de cabeza; Bash se vuelve frágil cuando intentas convertirlo en algo que no es. Tercero: ¿necesitas distribuirlo a personas que no usan la terminal? En ese caso el script forma parte de un producto y necesitas empaquetado, interfaz y control de versiones serio.

Muchos equipos acaban usando ambos enfoques en capas: Bash como capa de entrada, entorno y orquestación; otro lenguaje para el procesamiento pesado. No es una competición, es una división del trabajo sensata.

Fundamentos imprescindibles antes de tu primer script

El shebang y el modo estricto

Todo script empieza con una línea que indica al sistema qué intérprete debe ejecutarlo:

#!/usr/bin/env bash

La forma /usr/bin/env bash es preferible a /bin/bash porque localiza el intérprete en la ruta del usuario, lo que hace el script más portable entre distribuciones y contenedores. Después del shebang, la costumbre profesional es declarar el modo estricto:

set -euo pipefail

Esa única línea cambia el comportamiento de forma radical. -e hace que el script aborte cuando un comando falla. -u provoca un error si se usa una variable no definida, lo que evita fallos silenciosos por nombres mal escritos. -o pipefail hace que una tubería falle si falla cualquiera de sus comandos, no solo el último. Sin estas tres opciones un script puede seguir avanzando después de un error grave y producir resultados corruptos sin avisar.

Permisos y tres formas de ejecutar un script

Un archivo de script no se ejecuta por sí solo: necesita permisos.

chmod +x mi_script.sh
./mi_script.sh

La primera forma, ejecutarlo como programa, respeta el shebang y es la más limpia. La segunda, bash mi_script.sh, sirve cuando el archivo no tiene permisos de ejecución o cuando quieres forzar un intérprete concreto. La tercera, source mi_script.sh (o . mi_script.sh), ejecuta el contenido en la sesión actual: es muy útil para cargar funciones y variables del entorno, pero es peligrosa si el script contiene exit, porque cerrará tu terminal. Conoce la diferencia antes de usar source en un archivo que no controlas.

Variables, comillas y sustitución de comandos

Las variables se asignan sin espacios alrededor del signo igual:

nombre="proyecto"
ruta_base="$HOME/workspace"
fecha=$(date +%Y-%m-%d_%H-%M)

Las comillas dobles permiten la expansión de variables; las simples no. Esa distinción es la causa número uno de errores en scripts novatos:

echo "Hoy es $fecha"     # expande la variable
echo 'Hoy es $fecha'     # imprime el texto literal

Usa siempre comillas dobles alrededor de rutas y variables. Si una ruta contiene espacios, rm -rf $carpeta puede borrar algo que no querías; rm -rf "$carpeta" no. Son dos caracteres que pueden salvarte un directorio entero. La sustitución con $(...) es la forma moderna y anidable; la sintaxis antigua con acentos graves aparece todavía en código heredado, pero no la uses en scripts nuevos porque se anida mal y se lee peor.

Condicionales, bucles y funciones

El condicional básico se escribe con if, then, elif, else y fi. Para comparar cadenas usa [[ ]], más seguro que [ ]:

if [[ -f "$archivo" ]]; then
  echo "El archivo existe"
elif [[ -d "$archivo" ]]; then
  echo "Es un directorio"
else
  echo "No se encontró"
fi

Los bucles for brillan recorriendo archivos y los while leyendo líneas de un archivo o de una tubería:

for f in *.mp4; do
  echo "Procesando $f"
done

while IFS= read -r linea; do
  echo "$linea"
done < lista.txt

El patrón IFS= read -r es la forma correcta de leer líneas sin perder espacios ni interpretar barras invertidas. Las funciones se declaran con nombre() { ... } y son la diferencia entre un script de veinte líneas y un monolito imposible de mantener. Cada función debería hacer una sola cosa y tener un nombre que describa esa cosa.

Control de pantalla: clear, tput y printf sin sorpresas

Qué hace realmente clear

clear consulta la base de datos de terminales, envía la secuencia de escape adecuada y deja la pantalla vacía con el cursor arriba. Funciona bien, pero tiene un matiz que casi nadie conoce: en la mayoría de terminales solo desplaza el contenido hacia el búfer de desplazamiento. Si haces scroll hacia arriba, verás todo lo anterior. Para muchos scripts eso es aceptable; para una demostración grabada, no.

Alternativas y cuándo usar cada una

  • tput clear hace lo mismo pero consultando la base de terminales de forma explícita, lo que ayuda en entornos restringidos donde clear no está en la ruta.
  • tput reset o reset reinician el terminal por completo, algo necesario cuando la salida se ha corrompido tras imprimir un archivo binario por accidente.
  • printf '\033c' envía una secuencia de reinicio y suele limpiar también el búfer de desplazamiento.
  • clear -x conserva el búfer de desplazamiento en las implementaciones que lo soportan.
  • tput cup 0 0 mueve el cursor a la esquina superior izquierda sin borrar, útil para redibujar una interfaz de texto sencilla.

Limpiar solo cuando hay una terminal real

Limpiar la pantalla es una decisión de experiencia, no solo de estética. Tiene sentido antes de una demo, antes de un menú interactivo, al inicio de una tarea larga con barra de progreso o después de un bloque de salida muy ruidoso. No tiene sentido dentro de un bucle que se ejecuta quinientas veces, ni en un script que corre en un sistema de integración continua donde la salida se captura en un log: los códigos de escape ensucian el archivo y rompen los visores.

El patrón profesional es detectar si la salida es interactiva antes de limpiar:

if [[ -t 1 ]]; then
  clear
fi

La prueba -t 1 comprueba si la salida estándar es una terminal real. Aplica la misma idea a los colores, a los caracteres decorativos y a las barras de progreso. Es una de esas mejoras pequeñas que separan un script casero de una herramienta que puedes compartir sin vergüenza.

Proyecto guiado: script de limpieza de terminal paso a paso

Diseño y requisitos

Vamos a construir un script pequeño pero bien terminado. Objetivos: limpiar la pantalla de forma segura, aceptar un mensaje opcional, registrar cuándo se ejecutó y ser idempotente, es decir, que ejecutarlo dos veces no rompa nada ni duplique efectos.

Código completo

#!/usr/bin/env bash
set -euo pipefail

LOG_DIR="${HOME}/.local/state/clean_screen"
LOG_FILE="${LOG_DIR}/historial.log"
MENSAJE="${1:-Listo. Sesión limpia.}"

mkdir -p "$LOG_DIR"

limpiar_pantalla() {
  if [[ -t 1 ]]; then
    if command -v tput >/dev/null 2>&1; then
      tput reset
    else
      printf '\\033c'
    fi
    echo "${MENSAJE}"
  else
    echo "[clean_screen] Salida no interactiva: no se limpia la pantalla."
  fi
}

registrar() {
  printf '%s | pid=%s | usuario=%s\n' "$(date -Iseconds)" "$$" "${USER:-desconocido}" >> "$LOG_FILE"
}

rotar_log() {
  if [[ -f "$LOG_FILE" ]] && [[ $(wc -l < "$LOG_FILE") -gt 500 ]]; then
    mv "$LOG_FILE" "${LOG_FILE}.1"
    : > "$LOG_FILE"
  fi
}

principal() {
  registrar
  rotar_log
  limpiar_pantalla
}

principal "$@"

Cada función cumple una responsabilidad concreta. limpiar_pantalla decide si debe actuar. registrar deja rastro de uso, algo que agradecerás cuando quieras entender por qué tu terminal se comporta de forma extraña. rotar_log evita que el archivo crezca sin control, un problema clásico de la automatización doméstica. Y principal mantiene el orden de las operaciones en un solo lugar visible.

Instalarlo como comando global

Para invocarlo desde cualquier directorio, guárdalo en una carpeta incluida en tu ruta y dale permisos:

mkdir -p "$HOME/bin"
mv clean_screen.sh "$HOME/bin/clean_screen"
chmod +x "$HOME/bin/clean_screen"
export PATH="$HOME/bin:$PATH"

Añade la línea de PATH a tu archivo de perfil (.bashrc, .zshrc o el equivalente de tu shell) para que persista entre sesiones. A partir de ahí puedes escribir clean_screen y funciona como cualquier otro comando del sistema.

Cómo probarlo sin riesgo

Antes de confiar en cualquier script que mueva o borre archivos, practica en un directorio desechable. Crea una carpeta temporal, copia dentro una muestra de archivos con nombres raros —espacios, acentos, saltos de línea si te atreves— y ejecuta ahí la primera versión. Añade siempre una opción de simulación:

simular=true
rm_simulado() {
  if [[ "$simular" == true ]]; then
    echo "[simulación] se borraría: $1"
  else
    rm -rf -- "$1"
  fi
}

El -- final evita que un nombre que empiece por guion se interprete como una opción. Es un detalle minúsculo con consecuencias desproporcionadas.

Entrada, salida y errores: cómo hacer scripts fiables

Parsear argumentos con while y case

Un script útil acepta opciones. El bucle while con case sigue siendo la forma más portable y legible:

verbose=false
salida=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    -v|--verbose) verbose=true; shift ;;
    -o|--output) salida="$2"; shift 2 ;;
    -h|--help) echo "Uso: $0 [-v] [-o archivo]"; exit 0 ;;
    *) echo "Opción desconocida: $1" >&2; exit 2 ;;
  esac
done

Dos detalles marcan la diferencia entre un script amateur y uno profesional. El primero es enviar los errores a la salida de error estándar con >&2, no a la salida normal. El segundo es devolver códigos de salida coherentes: cero para éxito, uno para fallo genérico, dos para uso incorrecto. Otros programas y tus propios scripts se apoyan en esas convenciones para encadenar decisiones.

Códigos de salida y encadenamiento

Encadenar con ; ejecuta el siguiente comando pase lo que pase. Encadenar con && solo continúa si el anterior tuvo éxito. Cuando un paso depende del anterior, usa &&. Cuando quieras un comportamiento explícito pase lo que pase, usa || para definir la alternativa. Y dentro de funciones, return 1 comunica el fallo sin matar el script entero si quien llama decide gestionarlo.

trap para limpiar al salir

El modo estricto cubre la mayoría de los casos, pero no todos. Cuando necesitas limpiar archivos temporales al salir, usa una trampa:

tmp_dir=$(mktemp -d)
limpiar() { rm -rf "$tmp_dir"; }
trap limpiar EXIT INT TERM

La trampa se ejecuta tanto si el script termina bien como si falla o recibe una señal. Es la manera correcta de no dejar basura en el directorio temporal cuando algo se interrumpe a mitad de camino.

Logs útiles y salida estructurada

Redirigir es parte del lenguaje: > sobrescribe, >> añade, 2> captura errores, &> combina ambos. Para scripts de larga duración, registra con marca de tiempo sin perder la salida en pantalla:

log() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*" | tee -a "$LOG_FILE"; }

Si el destino del informe es otro programa, prefiere formatos estructurados: una línea de CSV o un objeto JSON por evento es infinitamente más fácil de procesar que un párrafo en lenguaje natural. Y si el log va a crecer, planifica la rotación desde el principio, no cuando el archivo tenga dos millones de líneas.

Mantenimiento del entorno: copias, cachés y tareas programadas

Copias de seguridad incrementales

Un script de respaldo bien hecho no copia todo cada vez. Copia solo lo que cambió y aprovecha enlaces duros para no multiplicar el consumo:

origen="$HOME/proyectos"
destino="$HOME/backups/$(date +%Y/%m)"
mkdir -p "$destino"
rsync -a --delete --link-dest="$destino/ultimo" "$origen/" "$destino/copia_$(date +%H%M)/"

Con --link-dest, los archivos sin cambios comparten espacio en disco. Es la forma más eficiente de mantener un historial largo sin llenar el disco. Antes de usarlo en producción, verifica siempre con --dry-run qué se va a borrar: el modificador --delete es potente y no pregunta.

Limpieza de cachés con simulación previa

Los entornos de desarrollo acumulan basura con rapidez: cachés de gestores de paquetes, renders intermedios, archivos de log gigantes. Un script semanal puede liberar gigabytes. Reglas de oro:

  • Nunca borres con comodines que apunten a la raíz del sistema o al directorio personal completo. Acota siempre a una carpeta concreta y conocida.
  • Verifica que la variable de ruta no esté vacía antes de usarla. Una variable vacía convierte una ruta absoluta en un borrado mucho más amplio de lo previsto.
  • Añade una opción de simulación que solo imprima lo que se eliminaría y ejecútala al menos una vez antes de confiar en el script.
  • Guarda durante unos días una lista de lo eliminado, por si tienes que recuperar algo por error.

cron y temporizadores del sistema

Para tareas periódicas, cron sigue siendo el estándar:

30 3 * * * "$HOME/bin/backup_proyectos" >> "$HOME/.local/state/backup.log" 2>&1

En sistemas con systemd, un temporizador ofrece más control, mejores registros y dependencias explícitas. Sea cual sea la opción, evita programar tareas pesadas en las horas en que necesitas la máquina al máximo rendimiento y asegúrate de que el script no depende de variables interactivas: cron arranca con un entorno mínimo y eso rompe muchos scripts que funcionan perfectamente a mano.

Bash conectado a APIs y servicios externos

curl y jq como pareja de baile

La combinación de curl para hacer peticiones y jq para filtrar JSON cubre una cantidad enorme de automatizaciones:

respuesta=$(curl -sS -H "Authorization: Bearer $TOKEN" \
  "https://api.ejemplo.com/v1/trabajos?estado=pendiente")

echo "$respuesta" | jq -r '.items[] | select(.peso_mb > 500) | .id' | while read -r id; do
  echo "Encolando $id"
done

Tres consejos que ahorran horas: usa -sS para silenciar la barra de progreso pero mostrar los errores reales; captura el código HTTP con -w '%{http_code}' y decídelo en consecuencia; y guarda los secretos en variables de entorno o en un gestor de secretos, nunca escritos dentro del script ni en un repositorio.

Reintentos con espera exponencial

Cuando dependes de un servicio externo, los fallos temporales son la norma, no la excepción. Un envoltorio de reintentos convierte un script frágil en uno tolerante:

reintentar() {
  local intento=1 max=5 espera=2
  until "$@"; do
    [[ $intento -ge $max ]] && return 1
    sleep "$espera"
    espera=$(( espera * 2 ))
    intento=$(( intento + 1 ))
  done
}

Este patrón tiene una de las mejores relaciones entre esfuerzo y beneficio de toda la automatización. Complementa con un límite de concurrencia —no lances doscientas peticiones a la vez— y con un archivo de fallos donde apuntar los elementos que no se pudieron procesar, para reprocesarlos después sin repetir todo.

Secretos y buenas prácticas de seguridad

Nunca escribas claves dentro de un script versionado. Usa variables de entorno, archivos con permisos restringidos (chmod 600) o un gestor de secretos. Tampoco imprimas el contenido de una variable sensible en los logs, ni siquiera en modo depuración: set -x muestra los valores expandidos y puede filtrar todo lo que no quieres filtrar. Si necesitas depurar, activa la traza solo en bloques concretos.

Bash aplicado a flujos de contenido multimedia

Las interfaces gráficas son cómodas, pero los proyectos de vídeo, audio e imagen generados con herramientas automáticas producen montañas de archivos con nombres impredecibles. Bash es excelente organizando ese caos sin tocar el criterio creativo.

Un flujo típico de postproducción automatizada incluye:

  1. Detectar archivos nuevos en una carpeta de descargas o de exportaciones.
  2. Validar que el archivo no esté a medio escribir comparando su tamaño en dos lecturas separadas por unos segundos.
  3. Renombrar con un esquema consistente: proyecto, escena, versión y marca temporal.
  4. Extraer metadatos con ffprobe y comprobar duración, resolución y códecs esperados.
  5. Mover el archivo a la carpeta correcta y registrar la operación en un índice CSV.
  6. Avisar al terminar con un resumen de lo procesado.
for archivo in *.mp4; do
  duracion=$(ffprobe -v error -show_entries format=duration \
    -of default=noprint_wrappers=1:nokey=1 "$archivo")
  ancho=$(ffprobe -v error -select_streams v:0 -show_entries stream=width \
    -of csv=p=0 "$archivo")
  printf '%s,%s,%s\n' "$archivo" "$duracion" "$ancho" >> inventario.csv
done

Ese inventario se convierte en la fuente de verdad del proyecto. Si más adelante necesitas transcribir, generar miniaturas o convertir formatos, ya tienes una lista fiable y no dependes de recordar dónde estaba cada cosa. La comprobación de archivos a medio escribir merece un párrafo aparte: muchos errores misteriosos vienen de procesar un archivo que todavía se está copiando. Comparar tamaños con una pausa de dos o tres segundos resuelve el noventa por ciento de esos casos.

La automatización no sustituye el criterio creativo: elimina la fricción para que dediques el tiempo a decidir en lugar de a ordenar carpetas.

Errores frecuentes, criterios de decisión y buenas prácticas

Estos son los fallos que aparecen una y otra vez en scripts reales, con su corrección:

  • No usar comillas. rm $archivo con espacios en el nombre se convierte en dos argumentos. Solución: comillas dobles siempre.
  • Confiar en el directorio actual. Un script que asume que se ejecuta desde su carpeta falla al llamarlo desde otro sitio. Solución: calcula su ruta con SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) y trabaja con rutas absolutas.
  • Ignorar códigos de salida. Encadenar todo con ; oculta fallos. Solución: && para dependencias y modo estricto como red de seguridad.
  • Escribir credenciales dentro del script. Solución: variables de entorno o archivos con permisos restringidos.
  • Parsear la salida de ls. Rompe con nombres que contienen saltos de línea. Solución: comodines del shell o find -print0 combinado con xargs -0.
  • No probar en un directorio desechable. Solución: crea una carpeta temporal con datos de ejemplo, especialmente si el script borra o mueve.
  • Hacer scripts gigantes. Un archivo de mil líneas es imposible de mantener. Solución: funciones cortas y, si crece demasiado, replantea el lenguaje.
  • No documentar. Un comentario de cinco líneas al inicio —qué hace, qué necesita, un ejemplo de uso— ahorra media hora de arqueología meses después.

Criterios rápidos de decisión

  • Tarea de orquestación de comandos: Bash.
  • Tarea con lógica de datos compleja: otro lenguaje con tipos.
  • Tarea que se ejecuta una vez al año: quizá no merezca script, sino una nota bien escrita.
  • Tarea que otros van a usar: añade --help, códigos de salida claros y un log.
  • Tarea que borra algo: añade simulación, comprobación de variable vacía y copia previa.

Preguntas frecuentes

¿Necesito saber programar para aprovechar Bash?

No en profundidad. Con variables, condicionales, bucles y funciones puedes automatizar la mayoría de las tareas del día a día. La curva se empina cuando entras en manejo avanzado de texto, pero utilidades como jq, awk o sed cubren buena parte de esos casos sin que tengas que reinventarlas desde cero.

¿Cómo depuro un script que falla solo en producción?

Activa la traza con bash -x script.sh y mira la última línea ejecutada antes del fallo. Después revisa el entorno: rutas, variables y permisos. La mayoría de los fallos que solo ocurren en producción vienen de diferencias en la ruta de búsqueda de ejecutables o de variables que no existen en ese contexto.

¿Es seguro usar rm -rf dentro de un script?

Solo con protecciones. Comprueba que la variable no esté vacía, construye rutas absolutas a partir de una base conocida, usa -- antes del nombre y ejecuta primero una versión con simulación. El accidente típico es una variable vacía que convierte una ruta concreta en un borrado mucho más amplio.

¿Por qué mi script funciona a mano pero falla con cron?

Casi siempre es un problema de entorno. cron no carga tu perfil, así que la ruta de búsqueda es mínima y tus variables no existen. Solución: rutas absolutas para los ejecutables, declarar las variables necesarias al inicio del script y redirigir la salida a un archivo de log para leer el error real.

¿Cuánto debería durar un script típico?

Si un script supera las doscientas líneas o hace tres cosas claramente distintas, divídelo. Los scripts pequeños se prueban, se entienden y se corrigen; los grandes se convierten en cajas negras que nadie se atreve a tocar.

¿Cómo mantengo mis scripts organizados?

Trata la carpeta de scripts como un proyecto: nombres descriptivos, un bloque de ayuda por archivo, control de versiones y una carpeta de pruebas con datos de ejemplo. Documenta al inicio qué hace cada uno, qué requiere y un ejemplo de uso.

¿Merece la pena aprender awk y sed?

Merece la pena conocer lo básico: seleccionar columnas, sustituir patrones, filtrar líneas. No necesitas convertirte en experto. Con esos fundamentos resolverás el ochenta por ciento de los casos y evitarás escribir bucles frágiles para tareas que esas herramientas resuelven en una línea.

Si has llegado hasta aquí, ya tienes todo lo necesario para empezar: elige una tarea que repitas cada semana, escríbela en un script de veinte líneas, pruébala en una carpeta desechable y añádela a tu ruta. Cuando funcione, amplíala con ayuda, registro y tolerancia a fallos. Y cuando tengas cinco o seis scripts, verás que comparten patrones: funciones de log, validación de argumentos, manejo de errores. Ese es el momento de extraer una pequeña biblioteca común y reutilizarla en todo lo demás. El objetivo no es escribir más código, sino escribir menos y dedicar el tiempo recuperado a lo que sí aporta valor.

Alexander

Alexander