Bash scripting desde cero: guía completa para automatizar Linux

📅 Actualizado en febrero 2026 ✍️ Ángel López 📊 Nivel: Intermedio ⏱️ 35 min de lectura

Bash scripting es, sin exageración, la habilidad más práctica y rentable que puedes adquirir como usuario o administrador de Linux. Con un simple archivo de texto, puedes automatizar copias de seguridad que se ejecuten cada noche, procesar miles de archivos en segundos, monitorizar servidores y crear herramientas personalizadas que ahorren horas de trabajo manual. Esta guía te lleva desde los fundamentos absolutos hasta la escritura de scripts profesionales con ejemplos reales, diagramas, ejercicios resueltos y un proyecto integrador completo que podrás adaptar a tu propio entorno de trabajo.

🐚 ¿Qué es Bash?

Bash (acrónimo recursivo de Bourne Again SHell) es el intérprete de comandos por defecto en la inmensa mayoría de distribuciones Linux y en macOS hasta Catalina (2019). Fue creado por Brian Fox en 1989 dentro del proyecto GNU de Richard Stallman, como un reemplazo libre y mejorado del Bourne Shell (sh) original de Unix. Hoy, cuando abres una terminal en Ubuntu, Debian, Mint, Fedora o CentOS, estás interactuando directamente con Bash.

Pero Bash no es solo un intérprete interactivo: es también un lenguaje de programación completo. Soporta variables, condicionales, bucles, funciones, arrays, redirecciones, pipes y expansiones. Un script de Bash es simplemente un archivo de texto plano que contiene una secuencia de estos comandos. En lugar de escribir cada instrucción manualmente en la terminal, los guardas en un archivo .sh, le das permisos de ejecución y lo lanzas cuando quieras. Es como crear una receta: escribes los pasos una vez y los repites cuantas veces necesites, de forma idéntica y sin errores humanos.

La potencia de Bash reside en su capacidad para combinar herramientas pequeñas y especializadas en flujos de trabajo complejos. Esta filosofía, heredada directamente de Unix, se conoce como el principio de composición: cada programa hace una sola cosa bien, y tú los conectas mediante pipes (|) para resolver problemas mayores. Un solo comando de Bash puede encadenar find, grep, sort, awk y sed en una línea que procese gigabytes de logs en pocos segundos.

Ken Thompson (sentado) y Dennis Ritchie (de pie) frente al PDP-11 en los laboratorios Bell, donde crearon Unix en los años 70
Ken Thompson (sentado) y Dennis Ritchie frente al PDP-11 en los laboratorios Bell, c. 1973. Su trabajo en Unix sentó los cimientos de la tradición shell que desembocó en Bash. — Foto: Wikimedia Commons, CC BY-SA 2.0.

La historia de Bash está profundamente entrelazada con la de Unix. En 1971, Ken Thompson escribió el primer shell de Unix, un programa minimalista que permitía ejecutar comandos uno a uno. En 1977, Stephen Bourne, trabajando en los laboratorios Bell de AT&T, creó el Bourne Shell (sh), que añadió capacidades de programación: variables, condicionales y bucles. Sin embargo, sh era software propietario de AT&T. Cuando Richard Stallman inició el proyecto GNU en 1983 para construir un sistema operativo completamente libre, necesitaba un shell libre. Brian Fox aceptó el reto y, tras varios años de desarrollo, anunció la primera versión beta de Bash en junio de 1989. El nombre — Bourne Again — es un juego de palabras con «born again» (renacer), señalando que era el renacimiento libre del Bourne Shell original.

Evolución de los shells Unix sh Thompson1971 · Bell Labs sh Bourne1977 · AT&T csh 1978 ksh 1983 bash GNU Bash1989 · Brian FoxFree Software Foundation ✓ Defecto zsh Z Shell1990 · macOS fish Fish Shell2005 · Moderno © Ciberaula 2026 · Evolución histórica de los shells Unix/Linux
Evolución histórica de los shells de Unix y Linux, desde el Thompson Shell original (1971) hasta Fish (2005). Bash ocupa la posición central como el shell más utilizado en el ecosistema Linux.
Evolución de los shells Unix sh Thompson1971 · Bell Labs sh Bourne1977 · AT&T csh 1978 ksh 1983 bash GNU Bash1989 · Brian FoxFree Software Foundation ✓ Defecto zsh Z Shell1990 · macOS fish Fish Shell2005 · Moderno © Ciberaula 2026 · Evolución histórica de los shells Unix/Linux
Evolución histórica de los shells de Unix y Linux, desde el Thompson Shell original (1971) hasta Fish (2005). Bash ocupa la posición central como el shell más utilizado en el ecosistema Linux.
CaracterísticaBashPythonPowerShell
Plataforma nativaLinux/macOSMultiplataformaWindows
Ideal paraTareas de sistema, archivos, pipesLógica compleja, APIs, datosAdministración Windows
Curva de aprendizajeBaja para lo básicoBaja-mediaMedia
Uso en servidoresUbicuo (96%+ servidores)Muy extendidoSolo entornos Windows
Procesamiento de textoExcelente (grep, sed, awk)BuenoBueno
Manipulación de archivosNativo y rapidísimoRequiere módulos (os, shutil)Bueno (cmdlets)
💡 ¿Cuándo usar Bash vs Python?
La regla de oro es sencilla: usa Bash cuando tu tarea consista principalmente en combinar comandos del sistema — mover archivos, buscar texto, gestionar procesos, programar cron jobs, hacer backups. Usa Python cuando necesites lógica de negocio compleja, estructuras de datos avanzadas (diccionarios, clases), conexiones a APIs REST o procesamiento matemático/estadístico. En la práctica profesional, un buen sysadmin domina ambos y elige la herramienta adecuada para cada tarea.

🚀 Tu primer script

Crear un script de Bash requiere solo tres pasos: crear el archivo, escribir los comandos y darle permisos de ejecución. No necesitas un IDE, un compilador ni librerías externas. Con cualquier editor de texto — desde nano en la terminal hasta VS Code — puedes empezar a programar inmediatamente.

hola.sh — Tu primer script
#!/bin/bash # Mi primer script de Bash # Autor: estudiante de Ciberaula # Fecha: febrero 2026 echo "¡Hola, mundo!" echo "Hoy es $(date '+%d de %B de %Y')" echo "Estás conectado como: $(whoami)" echo "En el directorio: $(pwd)" echo "Tu sistema: $(uname -o) $(uname -r)"
terminal — ejecutar el script
# 1. Dar permisos de ejecución chmod +x hola.sh # 2. Ejecutar ./hola.sh # Salida: # ¡Hola, mundo! # Hoy es 25 de febrero de 2026 # Estás conectado como: ana # En el directorio: /home/ana # Tu sistema: GNU/Linux 6.5.0-44-generic

La primera línea del script (#!/bin/bash) se denomina shebang (contracción de sharp-bang: almohadilla + exclamación). Es una directiva que le indica al kernel de Linux qué intérprete debe usarse para ejecutar el archivo. Sin ella, el sistema utilizará el shell por defecto del usuario, que podría no ser Bash — en macOS moderno, por ejemplo, el shell por defecto es Zsh. Por seguridad y portabilidad, incluye siempre el shebang como primera línea de tus scripts.

Una alternativa más portable es usar #!/usr/bin/env bash, que busca el ejecutable de Bash en el PATH del sistema. Esto es útil si Bash no está instalado exactamente en /bin/bash, algo que puede ocurrir en algunos entornos como FreeBSD o instalaciones personalizadas.

Anatomía de un script Bash backup.sh 1#!/bin/bash 2# Backup automático 3 4ORIGEN="/home/ana/docs" 5DESTINO="/backup" 6 7copiar() { 8tar -czf "$DESTINO" "$ORIGEN" 9} 10 11if [ -d "$ORIGEN" ]; then 12copiar 13else 14echo "Error: no existe" 15fi ① Shebang ② Comentarios ③ Variables ④ FuncionesBloques reutilizables ⑤ Control de flujoif/else, for, while, case 📝 Buenas prácticas: Shebang en L1 · Variables en MAYÚSCULAS · Funciones antes de lógica © Ciberaula 2026 · Anatomía de un script Bash
Anatomía de un script Bash: los cinco componentes fundamentales — shebang, comentarios, variables, funciones y control de flujo.
Anatomía de un script Bash backup.sh 1#!/bin/bash 2# Backup automático 3 4ORIGEN="/home/ana/docs" 5DESTINO="/backup" 6 7copiar() { 8tar -czf "$DESTINO" "$ORIGEN" 9} 10 11if [ -d "$ORIGEN" ]; then 12copiar 13else 14echo "Error: no existe" 15fi ① Shebang ② Comentarios ③ Variables ④ FuncionesBloques reutilizables ⑤ Control de flujoif/else, for, while, case 📝 Buenas prácticas: Shebang en L1 · Variables en MAYÚSCULAS · Funciones antes de lógica © Ciberaula 2026 · Anatomía de un script Bash
Anatomía de un script Bash: los cinco componentes fundamentales — shebang, comentarios, variables, funciones y control de flujo.
⚠️ Cuidado con los editores de Windows
Si editas un script en Windows y lo copias a Linux, puede fallar de forma silenciosa por los saltos de línea. Windows usa la secuencia \r\n (retorno de carro + nueva línea), mientras que Linux solo usa \n. El carácter \r invisible puede provocar errores como bad interpreter: No such file or directory incluso cuando el shebang parece correcto. Solución: ejecuta dos2unix script.sh o usa sed -i 's/\r$//' script.sh para limpiar el archivo.

📦 Variables y tipos de datos

En Bash, las variables se crean simplemente asignando un valor — sin declarar tipos, sin palabras reservadas especiales. Internamente, todo en Bash es texto; incluso los números se almacenan como cadenas de caracteres, aunque Bash puede operar con ellos aritméticamente cuando se le indica. Esta simplicidad es a la vez su mayor fortaleza (rapidez para crear scripts) y su mayor trampa (errores sutiles si no se respetan las convenciones).

La regla más importante al asignar variables en Bash es: no dejes espacios alrededor del signo igual. Escribir nombre = "Ana" (con espacios) provocará un error, porque Bash interpretará nombre como un comando y = como su primer argumento. La forma correcta es siempre nombre="Ana", pegado y sin espacios.

variables.sh — Tipos y usos de variables
#!/bin/bash # Variables de texto (strings) nombre="Ana García" distro="Ubuntu 24.04 LTS" # Variables numéricas (enteros) edad=30 puerto=8080 # Sustitución de comandos: capturar salida fecha_actual="$(date '+%Y-%m-%d')" num_usuarios="$(who | wc -l)" ip_local="$(hostname -I | awk '{print $1}')" # Usar variables (siempre con comillas dobles) echo "Hola, $nombre" echo "Distribución: $distro" echo "Fecha: $fecha_actual" echo "Usuarios conectados: $num_usuarios" echo "IP local: $ip_local" # Aritmética con (( )) a=15 b=4 echo "Suma: $(( a + b ))" # 19 echo "División: $(( a / b ))" # 3 (entera) echo "Módulo: $(( a % b ))" # 3 # Variables de entorno (exportadas a procesos hijos) export PROYECTO="mi-servidor" export LOG_LEVEL="debug"

Existen dos formas de expandir variables: $variable y ${variable}. La segunda forma, con llaves, es necesaria cuando la variable va seguida de caracteres que podrían confundir a Bash. Por ejemplo, "${archivo}_backup" funciona correctamente, mientras que "$archivo_backup" buscaría una variable llamada archivo_backup (que probablemente no existe). Como buena práctica, muchos programadores usan siempre las llaves para mayor claridad.

Tipo de variableSintaxisEjemploÁmbito
Local (script)VAR="valor"RUTA="/tmp"Solo el script actual
Exportadaexport VAR="valor"export PATH="..."Script + procesos hijos
Local (función)local var="valor"local resultado=0Solo la función
Especial (readonly)readonly VAR="valor"readonly PI=3Constante inmutable
Array indexadoARR=(a b c)frutas=(manzana pera)Acceso por índice: ${ARR[0]}
Array asociativodeclare -A MM[clave]="valor"Acceso por clave (Bash 4+)
✅ Buena práctica: siempre entrecomilla tus variables
Usa siempre "$variable" con comillas dobles. Sin comillas, si una variable contiene espacios (como un nombre de archivo con espacios), Bash la dividirá en varias palabras causando errores inesperados. La excepción es dentro de [[ ]] y (( )), donde Bash no hace word splitting — pero incluso ahí, las comillas no hacen daño y hacen el código más legible.

🔀 Condicionales: if, elif, else, case

Los condicionales permiten que un script tome decisiones: ejecutar un bloque de código u otro en función de si se cumple una condición. Son esenciales para scripts que deben reaccionar al estado del sistema — por ejemplo, comprobar si un archivo existe antes de procesarlo, si un servicio está activo antes de reiniciarlo, o si hay espacio suficiente en disco antes de iniciar una copia de seguridad.

Bash ofrece dos formas de evaluar condiciones: el comando test (que se abrevia como [ ]) y la construcción moderna [[ ]]. La segunda es recomendable en casi todos los casos porque maneja mejor las cadenas con espacios, soporta expresiones regulares con =~, y permite operadores lógicos como && y || directamente dentro de la condición.

condicionales.sh — Ejemplos completos
#!/bin/bash # Ejemplo 1: Comprobar si un archivo existe archivo="/etc/hosts" if [[ -f "$archivo" ]]; then echo "✓ El archivo $archivo existe" echo " Tamaño: $(stat --format='%s bytes' "$archivo")" echo " Última modificación: $(stat --format='%y' "$archivo" | cut -d. -f1)" else echo "✗ El archivo $archivo NO existe" exit 1 fi # Ejemplo 2: Comprobar espacio en disco uso_disco="$(df / --output=pcent | tail -1 | tr -d '% ')" if (( uso_disco >= 90 )); then echo "🔴 CRÍTICO: disco al ${uso_disco}%" elif (( uso_disco >= 70 )); then echo "🟡 AVISO: disco al ${uso_disco}%" else echo "🟢 OK: disco al ${uso_disco}%" fi # Ejemplo 3: case para opciones de menú echo "Elige acción: [i]nstalar [a]ctualizar [d]esinstalar" read -r opcion case "$opcion" in i|I|instalar) echo "Instalando..." ;; a|A|actualizar) echo "Actualizando..." ;; d|D|desinstalar) echo "Desinstalando..." ;; *) echo "Opción no reconocida: $opcion" exit 1 ;; esac
OperadorTipoSignificadoEjemplo
-fArchivoExiste y es fichero regular[[ -f "/etc/passwd" ]]
-dArchivoExiste y es directorio[[ -d "/var/log" ]]
-r / -w / -xArchivoPermisos lectura/escritura/ejecución[[ -w "$archivo" ]]
-zCadenaCadena vacía (longitud cero)[[ -z "$var" ]]
-nCadenaCadena no vacía[[ -n "$var" ]]
== / !=CadenaIgual / Distinto[[ "$a" == "$b" ]]
-eq -ne -lt -le -gt -geNúmeroComparaciones numéricas[[ "$x" -gt 10 ]]

🔁 Bucles: for, while, until

Los bucles son la estructura que transforma a Bash de un simple ejecutor de comandos en una herramienta de automatización real. Mientras que un condicional decide qué hacer, un bucle decide cuántas veces hacerlo. Son indispensables para procesar listas de archivos, recorrer líneas de un fichero, reintentar operaciones fallidas o monitorizar recursos del sistema de forma continua.

Bash ofrece tres tipos de bucle: for (itera sobre una lista), while (repite mientras la condición sea verdadera) y until (repite hasta que la condición sea verdadera). En la práctica, for y while cubren el 95% de los casos.

bucles.sh — Patrones profesionales
#!/bin/bash # FOR: recorrer archivos de un directorio echo "=== Archivos .log en /var/log ===" for archivo in /var/log/*.log; do if [[ -f "$archivo" ]]; then tamano="$(du -h "$archivo" | cut -f1)" echo " $archivo → $tamano" fi done # FOR con rango numérico (estilo C) echo "=== Tabla del 7 ===" for (( i=1; i<=10; i++ )); do printf " 7 × %2d = %2d\n" "$i" "$(( 7 * i ))" done # WHILE: leer fichero línea a línea echo "=== Usuarios con shell bash ===" while IFS=: read -r usuario _ _ _ _ _ shell; do if [[ "$shell" == "/bin/bash" ]]; then echo " $usuario" fi done < /etc/passwd # WHILE: monitorizar un proceso cada 5 segundos max_intentos=3 intento=0 while (( intento < max_intentos )); do if ping -c1 -W2 google.com &>/dev/null; then echo "✓ Conexión OK" break fi (( intento++ )) echo " Intento $intento de $max_intentos fallido..." sleep 2 done if (( intento == max_intentos )); then echo "✗ Sin conexión tras $max_intentos intentos" fi
💡 Truco: procesar la salida de un comando con while
Un patrón muy útil es usar while read combinado con la sustitución de procesos: while IFS= read -r linea; do ... done < <(comando). A diferencia de comando | while read (que abre un subshell donde las variables se pierden), la sustitución de procesos con <() mantiene las variables accesibles tras el bucle. Es un detalle técnico que marca la diferencia entre un script amateur y uno profesional.

🧩 Funciones

Las funciones son bloques de código reutilizables que encapsulan una tarea específica. En scripts de más de 50 líneas, son absolutamente imprescindibles para mantener el código organizado, legible y mantenible. Una función en Bash se declara con nombre() { ... } y se invoca simplemente escribiendo su nombre, como si fuera un comando más del sistema.

A diferencia de la mayoría de lenguajes de programación, las funciones de Bash no declaran parámetros con nombre. Los argumentos se pasan por posición y se acceden dentro de la función con $1, $2, etc. — igual que los argumentos de un script completo. La variable $# contiene el número de argumentos recibidos, y $@ los contiene todos como una lista.

funciones.sh — Funciones profesionales
#!/bin/bash # Función con validación de argumentos log() { local nivel="${1:?Falta nivel: info|warn|error}" local mensaje="${2:?Falta mensaje}" local timestamp="$(date '+%Y-%m-%d %H:%M:%S')" case "$nivel" in info) printf "[%s] \033[32mINFO\033[0m %s\n" "$timestamp" "$mensaje" ;; warn) printf "[%s] \033[33mWARN\033[0m %s\n" "$timestamp" "$mensaje" ;; error) printf "[%s] \033[31mERROR\033[0m %s\n" "$timestamp" "$mensaje" >&2 ;; *) printf "[%s] %s %s\n" "$timestamp" "$nivel" "$mensaje" ;; esac } # Función que retorna un código de salida comprobar_servicio() { local servicio="$1" if systemctl is-active --quiet "$servicio" 2>/dev/null; then log info "Servicio '$servicio' activo" return 0 else log warn "Servicio '$servicio' inactivo" return 1 fi } # Uso log info "Script iniciado" for srv in nginx mysql ssh; do comprobar_servicio "$srv" done log info "Script finalizado"
✅ Buena práctica: usa local para todas las variables dentro de funciones
Sin local, las variables creadas dentro de una función son globales y pueden sobrescribir accidentalmente variables del mismo nombre en el ámbito principal. Esta es una de las fuentes de bugs más comunes en scripts Bash complejos. Declara siempre tus variables de función con local para aislar su ámbito.

📥 Entrada de usuario y argumentos

Un script profesional debe ser flexible: capaz de recibir datos tanto desde la línea de comandos (argumentos posicionales) como de forma interactiva (pidiendo entrada al usuario). Dominar ambas técnicas permite crear herramientas versátiles que se adaptan tanto al uso manual como a la automatización con cron o systemd timers.

entrada.sh — Argumentos y entrada interactiva
#!/bin/bash # Parámetros posicionales echo "Nombre del script: $0" echo "Primer argumento: ${1:-<no proporcionado>}" echo "Segundo argumento: ${2:-<no proporcionado>}" echo "Total de argumentos: $#" echo "Todos los argumentos: $@" # Validar número mínimo de argumentos if (( $# < 1 )); then echo "Uso: $0 <directorio> [extensión]" >&2 exit 1 fi # Lectura interactiva con read read -rp "¿Confirmar la operación? [s/n]: " respuesta if [[ "$respuesta" =~ ^[sS]$ ]]; then echo "Procesando..." else echo "Cancelado por el usuario" exit 0 fi # Lectura de contraseña (sin eco en pantalla) read -rsp "Introduce tu contraseña: " password echo # salto de línea tras la entrada oculta echo "Contraseña recibida (${#password} caracteres)"

🔍 Procesamiento de texto: grep, sed, awk

Si hay una habilidad que distingue a un usuario avanzado de Linux de un principiante, es el dominio de la tríada grep, sed y awk. Estas tres herramientas, todas con décadas de historia en el ecosistema Unix, permiten buscar, filtrar, transformar y analizar texto con una potencia y velocidad que difícilmente igualan los lenguajes de programación modernos cuando se trata de procesar archivos de log, configuraciones o datos tabulares directamente desde la terminal.

grep (Global Regular Expression Print) busca líneas que coincidan con un patrón. sed (Stream Editor) transforma texto sobre la marcha: reemplaza cadenas, elimina líneas, inserta contenido. awk es un lenguaje de procesamiento de campos orientado a columnas, ideal para datos estructurados en columnas separadas por espacios o caracteres delimitadores.

Pipeline de procesamiento de texto cat access.log | grep "ERROR" | sed 's/2026/****/' | awk '{print $1, $NF}' 📄 ARCHIVO access.log 10.000 líneas grep Filtrar líneas "ERROR" → 247 líneas sed Transformar texto s/2026/****/ → 247 líneas (editadas) awk Extraer columnas {print $1, $NF} → IP + código HTTP Filosofía Unix: cada herramienta hace una sola cosa bien grep → busca y filtra líneas sed → edita texto sobre la marcha awk → procesa campos y columnas Conectadas mediante pipes ( | ) los datos fluyen de izquierda a derecha © Ciberaula 2026 · Pipeline de procesamiento de texto
Pipeline de procesamiento de texto Unix: grep filtra, sed transforma y awk extrae columnas. Conectados con pipes, procesan miles de líneas en milisegundos.
Pipeline de procesamiento de texto cat access.log | grep "ERROR" | sed 's/2026/****/' | awk '{print $1, $NF}' 📄 ARCHIVO access.log 10.000 líneas grep Filtrar líneas "ERROR" → 247 líneas sed Transformar texto s/2026/****/ → 247 líneas (editadas) awk Extraer columnas {print $1, $NF} → IP + código HTTP Filosofía Unix: cada herramienta hace una sola cosa bien grep → busca y filtra líneas sed → edita texto sobre la marcha awk → procesa campos y columnas Conectadas mediante pipes ( | ) los datos fluyen de izquierda a derecha © Ciberaula 2026 · Pipeline de procesamiento de texto
Pipeline de procesamiento de texto Unix: grep filtra, sed transforma y awk extrae columnas. Conectados con pipes, procesan miles de líneas en milisegundos.
texto.sh — grep, sed, awk en acción
#!/bin/bash # ──── grep: buscar y filtrar ──── # Buscar errores en logs (case-insensitive, con número de línea) grep -in "error\|failed\|critical" /var/log/syslog # Buscar archivos PHP que contengan "mysql_query" (obsoleto) grep -rl "mysql_query" /var/www/ --include="*.php" # Contar cuántas veces aparece cada IP en access.log grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.log | sort | uniq -c | sort -rn | head 10 # ──── sed: transformar texto ──── # Reemplazar texto en un archivo (in-place con backup) sed -i.bak 's/localhost/192.168.1.100/g' config.ini # Eliminar líneas vacías y comentarios de un fichero de configuración sed '/^$/d; /^#/d' /etc/ssh/sshd_config # Insertar texto después de una línea específica sed '/\[mysqld\]/a max_connections = 500' /etc/mysql/my.cnf # ──── awk: procesar columnas ──── # Mostrar usuarios y sus shells awk -F: '{printf "%-20s %s\n", $1, $7}' /etc/passwd # Sumar el tamaño de archivos .log ls -l *.log | awk '{total += $5} END {printf "Total: %.2f MB\n", total/1024/1024}' # Analizar access.log: top 5 URLs más visitadas awk '{print $7}' access.log | sort | uniq -c | sort -rn | head 5

🐛 Depuración de scripts

Depurar scripts Bash puede ser frustrante si no conoces las herramientas disponibles. A diferencia de Python o Java, donde un error detiene la ejecución con un mensaje claro, Bash por defecto continúa ejecutando tras un error silencioso. Esta permisividad es peligrosa: un script de backup puede fallar en la copia pero seguir ejecutando la rotación, borrando archivos sin tener el respaldo. La solución es activar el modo estricto al inicio de todo script serio.

Modo estricto — primera línea tras el shebang
#!/bin/bash set -euo pipefail # -e → Detener al primer error (exit code ≠ 0) # -u → Error si se usa una variable no definida # -o pipefail → El pipe falla si falla cualquier comando, no solo el último # Para depuración, añade -x para ver cada comando antes de ejecutarse: # set -euxo pipefail # La -x imprime cada línea con un + antes de ejecutarla, mostrando # las expansiones de variables ya resueltas

La opción -x (xtrace) es tu mejor aliada durante el desarrollo. Muestra cada comando justo antes de ejecutarlo, con todas las variables ya expandidas a sus valores reales. Esto te permite ver exactamente qué está haciendo tu script en cada paso. Puedes activarla para una sección específica con set -x al inicio y set +x al final, sin necesidad de depurar todo el script.

TécnicaComandoCuándo usarla
Modo estrictoset -euo pipefailSiempre, en todos tus scripts
Traza de ejecuciónset -x / bash -x script.shCuando necesitas ver qué se ejecuta
Verificar sintaxisbash -n script.shAntes de ejecutar por primera vez
Linter estáticoshellcheck script.shSiempre, como paso de calidad
Trap para errorestrap 'echo "Error L$LINENO"' ERRScripts largos con múltiples fallos posibles
⚠️ Instala ShellCheck — tu mejor aliado
ShellCheck es un analizador estático de scripts Bash que detecta errores comunes, variables sin comillas, usos incorrectos de [ ] vs [[ ]], y decenas de malas prácticas. Puedes usarlo online o instalarlo con sudo apt install shellcheck. Ejecuta shellcheck script.sh antes de cada deploy — encontrarás bugs que ni una revisión manual detectaría.

✏️ Ejercicios resueltos

La programación solo se aprende practicando. Estos dos ejercicios están diseñados para integrar los conceptos vistos: variables, condicionales, bucles, funciones y procesamiento de texto. Intenta resolverlos por tu cuenta antes de consultar la solución.

Ejercicio 1: Renombrador masivo de archivos

Enunciado: Escribe un script que reciba un directorio como argumento y renombre todos los archivos .jpeg a .jpg, mostrando un resumen de cuántos archivos se renombraron.

Ver solución del Ejercicio 1
renombrar.sh
#!/bin/bash set -euo pipefail directorio="${1:?Uso: $0 <directorio>}" contador=0 if [[ ! -d "$directorio" ]]; then echo "Error: '$directorio' no es un directorio" >&2 exit 1 fi for archivo in "$directorio"/*.jpeg; do [[ -f "$archivo" ]] || continue nuevo="${archivo%.jpeg}.jpg" mv -- "$archivo" "$nuevo" echo " ✓ $(basename "$archivo") → $(basename "$nuevo")" (( contador++ )) done echo "Resumen: $contador archivos renombrados"

Ejercicio 2: Monitor de espacio en disco

Enunciado: Crea un script que compruebe el uso de disco de todas las particiones montadas. Si alguna supera el 80%, debe generar una alerta con el formato [ALERTA] /dev/sda1 → 87% (montado en /).

Ver solución del Ejercicio 2
monitor_disco.sh
#!/bin/bash set -euo pipefail UMBRAL=80 alertas=0 echo "=== Monitor de disco — $(date '+%d/%m/%Y %H:%M') ===" while read -r dispositivo tamano usado disponible porcentaje montaje; do uso="${porcentaje%\%}" # quitar el símbolo % if (( uso >= UMBRAL )); then printf "[ALERTA] %-15s → %3s%% (montado en %s)\n" "$dispositivo" "$uso" "$montaje" (( alertas++ )) else printf "[ OK ] %-15s → %3s%% (montado en %s)\n" "$dispositivo" "$uso" "$montaje" fi done < <(df --output=source,size,used,avail,pcent,target -x tmpfs -x devtmpfs | tail -n +2) echo "--- Total alertas: $alertas ---"

🎯 Proyecto integrador: backup automático con rotación

Este proyecto combina todo lo aprendido en un script profesional que podrías desplegar hoy en un servidor de producción. El script comprime un directorio origen en un archivo .tar.gz con fecha, lo almacena en un directorio de destino, mantiene solo las últimas N copias (rotación automática) y genera un log de cada ejecución. Incluye validación de argumentos, manejo de errores con trap y funciones modulares.

backup.sh — Proyecto completo de backup con rotación
#!/bin/bash set -euo pipefail # ═══════════════════════════════════════════ # BACKUP AUTOMÁTICO CON ROTACIÓN # Uso: ./backup.sh /ruta/origen /ruta/destino [max_copias] # ═══════════════════════════════════════════ # --- Configuración --- ORIGEN="${1:?Uso: $0 <origen> <destino> [max_copias]}" DESTINO="${2:?Falta directorio de destino}" MAX_COPIAS="${3:-7}" # por defecto, 7 backups FECHA="$(date '+%Y%m%d_%H%M%S')" NOMBRE_BASE="$(basename "$ORIGEN")" ARCHIVO="${DESTINO}/backup_${NOMBRE_BASE}_${FECHA}.tar.gz" LOG="${DESTINO}/backup.log" # --- Función de logging --- log() { local mensaje="[$(date '+%Y-%m-%d %H:%M:%S')] $1" echo "$mensaje" echo "$mensaje" >> "$LOG" } # --- Limpieza en caso de error --- cleanup() { if [[ -f "$ARCHIVO" ]] && [[ ! -s "$ARCHIVO" ]]; then rm -f "$ARCHIVO" log "ERROR: backup incompleto eliminado" fi } trap cleanup ERR # --- Validaciones --- if [[ ! -d "$ORIGEN" ]]; then log "ERROR: '$ORIGEN' no existe o no es directorio" exit 1 fi mkdir -p "$DESTINO" # --- Crear backup --- log "Iniciando backup de '$ORIGEN'..." inicio="$(date +%s)" tar -czf "$ARCHIVO" -C "$(dirname "$ORIGEN")" "$NOMBRE_BASE" fin="$(date +%s)" duracion="$(( fin - inicio ))" tamano="$(du -h "$ARCHIVO" | cut -f1)" log "✓ Backup completado: $ARCHIVO ($tamano, ${duracion}s)" # --- Rotación: eliminar los más antiguos --- num_backups="$(find "$DESTINO" -name "backup_${NOMBRE_BASE}_*.tar.gz" | wc -l)" if (( num_backups > MAX_COPIAS )); then eliminar="$(( num_backups - MAX_COPIAS ))" log "Rotación: eliminando $eliminar backup(s) antiguos..." find "$DESTINO" -name "backup_${NOMBRE_BASE}_*.tar.gz" -type f \ | sort | head -n "$eliminar" \ | while read -r viejo; do rm -f "$viejo" log " Eliminado: $(basename "$viejo")" done fi log "Backup finalizado. Copias conservadas: $MAX_COPIAS"
💡 Automatiza con cron
Para ejecutar este script cada día a las 3:00 AM, añade una entrada al crontab con crontab -e:
0 3 * * * /home/ana/scripts/backup.sh /var/www /backup 7 2>&1

⚠️ Errores frecuentes y buenas prácticas

Incluso programadores experimentados cometen errores en Bash debido a su sintaxis particular. Estos son los errores que más tiempo consumen en depuración, junto con la solución correcta para cada uno. Aprenderlos te ahorrará incontables horas de frustración.

ErrorCódigo incorrectoCódigo correctoExplicación
Espacios en asignaciónnombre = "Ana"nombre="Ana"Los espacios hacen que Bash trate nombre como comando
Variable sin comillasrm $archivorm "$archivo"Si tiene espacios, se divide en varias palabras
[ ] vs [[ ]][ $a == $b ][[ "$a" == "$b" ]][[ ]] no hace word splitting ni globbing
Comparar números[[ 10 > 9 ]](( 10 > 9 ))> en [[ ]] compara strings, no números
Command substitutionfecha=`date`fecha=$(date)Los backticks son obsoletos y no se anidan bien
Olvidar set -e#!/bin/bash#!/bin/bash
set -euo pipefail
Sin -e, los errores se ignoran silenciosamente
✅ Checklist de calidad para scripts Bash
Antes de considerar un script «terminado», verifica estos puntos: (1) tiene shebang #!/bin/bash en la línea 1; (2) usa set -euo pipefail; (3) todas las variables están entrecomilladas; (4) valida los argumentos de entrada; (5) usa funciones para bloques reutilizables; (6) pasa ShellCheck sin advertencias; (7) tiene comentarios en las secciones no obvias; (8) los mensajes de error van a stderr (>&2).

❓ Preguntas frecuentes sobre Bash scripting desde cero: guía completa para automatizar Linux

Las dudas más comunes respondidas de forma clara y directa.

Bash scripting es la escritura de programas (scripts) usando el intérprete de comandos Bash de Linux. Permite automatizar tareas repetitivas, combinar comandos del sistema y crear herramientas personalizadas. Es el lenguaje de automatización más extendido en servidores Linux y una habilidad fundamental para administradores de sistemas y DevOps.
No es necesario tener experiencia previa en programación. Bash tiene una sintaxis relativamente sencilla y, al tratarse de comandos del sistema, puedes ir aprendiendo de forma incremental: primero ejecutas comandos sueltos, luego los combinas en scripts simples, y poco a poco vas añadiendo lógica con variables, condicionales y bucles.
Shell es un término genérico para cualquier intérprete de comandos (sh, bash, zsh, fish, etc.). Bash (Bourne Again SHell) es una implementación concreta de shell y la más extendida en distribuciones Linux. Cuando alguien dice «script de shell» normalmente se refiere a un script de Bash.
Sí, mediante WSL (Windows Subsystem for Linux) puedes ejecutar Bash de forma nativa en Windows 10/11. También puedes usar Git Bash, que incluye una versión de Bash para Windows. Sin embargo, la experiencia completa se obtiene en un sistema Linux nativo.
Bash es ideal para tareas de sistema: mover archivos, ejecutar comandos, procesar texto y automatizar despliegues. Python es mejor para lógica compleja, procesamiento de datos, APIs y aplicaciones. Lo habitual en el mundo profesional es dominar ambos: Bash para tareas rápidas de sistema y Python para scripts más elaborados.
Hay dos formas principales: (1) darle permisos de ejecución con chmod +x script.sh y ejecutarlo con ./script.sh, o (2) invocarlo directamente con bash script.sh. La primera es la forma estándar en entornos de producción. Siempre debe empezar con la línea #!/bin/bash (shebang) para indicar el intérprete.
Las tres herramientas fundamentales son grep (buscar texto), sed (sustituir y transformar texto) y awk (procesar texto por columnas). Juntas forman la «santísima trinidad» del procesamiento de texto en Linux y se combinan constantemente con pipes (|) en scripts Bash.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Bash scripting desde cero: guía completa para automatizar Linux? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!

🚀 ¿Quieres dominar Linux profesionalmente?
Cursos bonificados por FUNDAE para empresas — formación 100% subvencionada
Ver cursos de Linux →