🐚 Bash y Shell Scripting — Cheatsheet Completo 🐚
Bash (Bourne Again Shell) es un shell de Unix y lenguaje de comandos compatible con POSIX. Es la interfaz de línea de comandos estándar de facto en la gran mayoría de distribuciones GNU/Linux y macOS (aunque las versiones recientes de macOS usan Zsh por defecto, Bash sigue estando ampliamente disponible). Dominar el scripting en Bash te permite automatizar tareas complejas del sistema operativo, gestionar despliegues en servidores, crear tuberías de integración continua (CI/CD) y conectar múltiples herramientas de consola de forma fluida.
1. 🌟 Conceptos Clave y Fundamentos
El intérprete de Bash opera mediante la lectura de comandos línea por línea. Comprender sus cimientos es vital antes de estructurar cualquier script de automatización:
- POSIX Compliance: Bash expande el estándar POSIX original del shell Bourne (
sh), añadiendo comodines avanzados, arreglos y mejoras sintácticas significativas. - Shebang (
#!): Línea inicial de un script que indica al núcleo del sistema operativo qué intérprete debe utilizar para ejecutar el archivo (ej.#!/usr/bin/env bash). - Código de Retorno (Exit Status / Return Code): Cada instrucción en Bash devuelve un valor entero del
0al255tras finalizar.- Significado: El valor
0representa éxito absoluto; cualquier número distinto de cero (1<=n<=255) indica un fallo o condición particular.
- Significado: El valor
- Proceso Hijo (Subshell): Al ejecutar un comando encerrado entre paréntesis
(comando)o mediante sustitución de comandos$(comando), se invoca una instancia hija del shell, la cual no puede alterar el entorno o las variables del proceso padre. - Modo Interactivo vs No Interactivo:
- Interactivo: Conectado a una terminal activa (recibe comandos del usuario mediante teclado).
- No Interactivo: Ejecuta scripts desde archivos en segundo plano o entornos CI/CD sin interfaz humana.
2.
Configuración Inicial y Entorno
Para crear, autorizar y ejecutar un script Bash en cualquier entorno Unix/Linux, debes seguir los pasos estándar del ciclo de vida del script:
2.1. Estructura Inicial del Archivo
Crea un archivo con extensión .sh o simplemente sin extensión (las extensiones no son obligatorias en Unix, pero ayudan a la organización). La primera línea debe apuntar de forma dinámica al ejecutable de Bash utilizando env:
#!/usr/bin/env bash
# Un script robusto siempre declara el shebang en la primera línea absoluta.
2.2. Concesión de Permisos de Ejecución
Los archivos nuevos se crean con permisos de lectura y escritura por defecto. Debes agregar el bit de ejecución para que el sistema operativo lo trate como un binario ejecutable:
# Asignar permisos de ejecución para el usuario actual
chmod +x mi_script.sh
# Ejecutar el script indicando la ruta relativa
./mi_script.sh
3. 📝 Sintaxis Básica y Variables
En Bash, todas las variables son tratadas conceptualmente como cadenas de caracteres (strings), a menos que se declare explícitamente un atributo de tipo numérico o arreglo.
3.1. Reglas de Declaración de Variables
- Sin espacios: No debe haber espacios antes o después del signo de asignación
=. - Case-Sensitive: Las variables diferencian mayúsculas de minúsculas.
VARes diferente devar. - Estilo: Por convención, las variables locales se escriben en
camelCaseosnake_caseminúscula, mientras que las variables de entorno globales se escriben enUPPERCASE.
# ASIGNACIÓN CORRECTA (Sin espacios alrededor del igual)
miNombre="Juan"
edad=30
# ASIGNACIÓN ERRÓNEA (Produce errores de comandos no encontrados)
# miNombre = "Juan"
# edad = 30
3.2. Acceso a Variables y Sustitución
Para recuperar el valor almacenado en una variable, se antepone el símbolo $. Se recomienda encarecidamente utilizar llaves ${var} para aislar el nombre de la variable de caracteres adyacentes:
# Lectura simple
echo $miNombre
# Uso de llaves para delimitar (Evita ambigüedades con caracteres adyacentes)
echo "Tengo ${edad}años de edad." # Imprime: Tengo 30años de edad.
# Comillas dobles vs Comillas simples
# Comillas dobles: Evalúan e interpolan variables y comandos
echo "Hola $miNombre" # Imprime: Hola Juan
# Comillas simples: Tratan el contenido como texto literal absoluto
echo 'Hola $miNombre' # Imprime: Hola $miNombre
3.3. Variables de Entorno y Exportación
Las variables definidas de forma estándar solo son visibles dentro del script actual. Si deseas que los subprocesos y comandos externos invoquen estas variables, debes exportarlas:
# Crear variable y hacerla accesible a procesos hijos
export API_KEY="secure_token_xyz"
3.4. Parámetros Posicionales y Variables Especiales
Bash asigna automáticamente argumentos pasados en la consola a variables reservadas de lectura:
| Variable | Descripción |
|---|---|
$0 | El nombre del script o comando que se está ejecutando. |
$1 … $9 | Argumentos del primero al noveno pasados al script. |
${10} … | Argumentos del décimo en adelante (requieren llaves obligatoriamente). |
$# | El número total de argumentos pasados al script. |
$* | Todos los argumentos juntos representados como un único string. |
$@ | Todos los argumentos representados como una lista ordenada e individualizada (Segura con bucles). |
$? | El código de retorno del último comando ejecutado (¡Crucial para control de errores!). |
$$ | El identificador de proceso (PID) del shell que ejecuta el script. |
# Ejemplo práctico de parámetros posicionales
# Ejecutado con: ./script.sh archivo.txt -v --force
echo "Nombre del script: $0" # ./script.sh
echo "Primer argumento: $1" # archivo.txt
echo "Segundo argumento: $2" # -v
echo "Cantidad de argumentos: $#" # 3
4. 🚦 Control de Flujo (Condicionales y Bucles)
Bash cuenta con múltiples herramientas para bifurcar y repetir la ejecución de tareas. Para evaluar condiciones de forma moderna y robusta, se utiliza la sintaxis doble corchete [[ ... ]] en lugar del corchete simple [ ... ] de POSIX.
4.1. Estructura Condicional if-elif-else
La sintaxis exige que las palabras clave then y fi delimiten el bloque condicional:
nota=85
if [[ $nota -ge 90 ]]; then
echo "Excelente calificacion"
elif [[ $nota -ge 70 ]]; then
echo "Aprobado satisfactoriamente"
else
echo "Reprobado"
fi
4.2. Operadores de Comparación
A diferencia de otros lenguajes, Bash utiliza operadores distintos para comparar texto y comparar números:
Comparadores de Cadenas de Texto (Strings)
[[ "$a" == "$b" ]]: Verdadero si las cadenas son idénticas.[[ "$a" != "$b" ]]: Verdadero si las cadenas son diferentes.[[ -z "$a" ]]: Verdadero si la cadena está vacía (longitud cero).[[ -n "$a" ]]: Verdadero si la cadena no está vacía (tiene contenido).
Comparadores Numéricos (Enteros únicamente)
-eq: Equal (Igual a)[[ $a -eq $b ]]-ne: Not Equal (Diferente de)[[ $a -ne $b ]]-lt: Less Than (Menor que)[[ $a -lt $b ]]-le: Less or Equal (Menor o igual que)[[ $a -le $b ]]-gt: Greater Than (Mayor que)[[ $a -gt $b ]]-ge: Greater or Equal (Mayor o igual que)[[ $a -ge $b ]]
Operadores de Archivos y Carpetas (¡Ultra útil para scripts de infraestructura!)
[[ -e "$path" ]]: Verdadero si la ruta existe.[[ -f "$path" ]]: Verdadero si la ruta existe y es un archivo regular.[[ -d "$path" ]]: Verdadero si la ruta existe y es un directorio (carpeta).[[ -r "$path" ]]: Verdadero si el archivo tiene permisos de lectura para el usuario actual.[[ -w "$path" ]]: Verdadero si el archivo tiene permisos de escritura.[[ -x "$path" ]]: Verdadero si el archivo tiene permisos de ejecución.
# Ejemplo práctico: Verificación de un archivo de configuración
config_
="/etc/myapp/config.yaml"
if [[ -f "$config_
" ]]; then
echo "El archivo existe. Leyendo configuracion..."
else
echo "Error: El archivo de configuracion no existe." >&2
exit 1
fi
4.3. Bucle for
Permite iterar sobre listas de valores, rangos numéricos o archivos generados dinámicamente:
# 1. Iterar sobre una lista estática de palabras
for tecnologia in Docker Kubernetes Terraform; do
echo "Tecnologia actual: ${tecnologia}"
done
# 2. Iterar sobre un rango numérico (Expansión de llaves)
for i in {1..5}; do
echo "Iteracion número: $i"
done
# 3. Iterar sobre archivos en un directorio (Globbing seguro)
# Evita usar $(ls *.sh) ya que los espacios en nombres de archivos romperían el script.
for script in *.sh; do
if [[ -f "$script" ]]; then
echo "Script encontrado: $script"
fi
done
4.4. Bucle while
Repite un bloque de código mientras la condición evaluada resulte exitosa (código de retorno 0). Es la herramienta perfecta para leer flujos de archivos línea por línea:
# Leer un archivo de texto de forma segura, línea por línea
archivo_logs="logs.txt"
# Usamos 'IFS=' para prevenir el recorte de espacios en blanco
# Usamos '-r' para evitar que se interpreten barras diagonales de escape
while IFS= read -r linea || [[ -n "$linea" ]]; do
echo "Procesando log: $linea"
done < "$archivo_logs"
5. ⚙️ Funciones en Bash
Las funciones permiten modularizar scripts y evitar la duplicación de código. En Bash, las funciones no tienen firmas de parámetros explícitas; en su lugar, capturan los parámetros utilizando los mismos parámetros posicionales que el propio script ($1, $2, $@).
5.1. Declaración y Llamada de Funciones
Las funciones se pueden declarar utilizando la sintaxis estándar Unix o anteponiendo la palabra clave function:
# Forma estándar recomendada (Compatible con POSIX)
log_info() {
# Definición de variables locales para evitar contaminar el entorno global
local mensaje="$1"
local fecha
fecha=$(date "+%Y-%m-%d %H:%M:%S")
echo "[INFO] [${fecha}] ${mensaje}"
}
# Llamada a la función (¡Sin usar paréntesis!)
log_info "El servidor web ha iniciado con éxito."
5.2. Retorno de Valores en Funciones
Las funciones en Bash no devuelven objetos o textos directamente mediante return. La palabra clave return solo devuelve un código de salida numérico (0 a 255):
# Comprobación de salud de una API
verificar_servicio() {
local url="$1"
# curl silencioso (-s) con salida de cabeceras (-I)
if curl -s -I "$url" | grep -q "HTTP/1.1 200 OK"; then
return 0 # Éxito
else
return 1 # Fallo
fi
}
# Uso de la función
if verificar_servicio "https://api.github.com"; then
echo "GitHub esta disponible"
else
echo "Error: GitHub caido o inaccesible"
fi
5.3. Devolver Cadenas de Texto (Strings) desde una Función
Para simular el retorno de un valor tipo texto, la función escribe su resultado en la salida estándar (stdout) y el script principal captura dicha salida mediante sustitución de comandos $(...):
generar_nombre_backup() {
local prefijo="$1"
local timestamp
timestamp=$(date "+%Y%m%d_%H%M%S")
# Escribimos el resultado en stdout
echo "${prefijo}_backup_${timestamp}.tar.gz"
}
# Capturamos el stdout de la función en una variable externa
archivo_final=$(generar_nombre_backup "base_datos")
echo "Se creara el archivo: ${archivo_final}"
6.
Manipulación de Archivos y Directorios
El núcleo del scripting en Bash consiste en la manipulación y gestión del sistema de archivos. A continuación, se detallan las operaciones esenciales:
6.1. Creación Segura y Estructurada
Asegúrate de no sobrescribir directorios o generar errores en cascada si un recurso ya existe:
# Crear un árbol de directorios recursivo sin arrojar errores si ya existen (-p)
mkdir -p /tmp/app/logs/archived
# Crear un archivo temporal seguro con nombre único aleatorio
archivo_temp=$(mktemp /tmp/mi_script_XXXXXX.txt)
echo "Trabajando en archivo seguro: $archivo_temp"
# Eliminar el archivo temporal al final de la ejecución usando un trap
trap 'rm -f "$archivo_temp"' EXIT
6.2. Copias, Desplazamientos y Limpieza
Automatiza copias de seguridad de forma óptima:
# Copia recursiva preservando metadatos (fechas, permisos) usando (-a)
cp -a /var/www/html /backup/web_root
# Mover archivos forzando la sobreescritura sin preguntar (-f)
mv -f /tmp/config.json /etc/myapp/config.json
# Buscar y borrar archivos modificados hace más de 30 días recursivamente
find /var/log/myapp -type f -name "*.log" -mtime +30 -exec rm {} \;
7. 📥 Entrada, Salida y Redirección (I/O)
Unix define tres flujos de datos estándar que interactúan constantemente con los comandos de consola:
- Entrada Estándar (
stdin): Descriptor de archivo0. - Salida Estándar (
stdout): Descriptor de archivo1. - Error Estándar (
stderr): Descriptor de archivo2.
7.1. Operadores de Redirección Comunes
>: Redirigestdouta un archivo, sobrescribiendo su contenido actual.>>: Redirigestdouta un archivo, anexando el contenido al final.2>: Redirige únicamente los mensajes de error (stderr) a un archivo.2>&1: Fusiona el flujo destderrcon el destdoutpara procesar ambos juntos.> /dev/null: Descarta la salida estándar (silencia el comando).2> /dev/null: Descarta los mensajes de error.&> /dev/null: Silencia de manera absoluta el comando (descarta tantostdoutcomostderr).
# 1. Guardar la lista de procesos activos sobrescribiendo el archivo
ps aux > procesos.txt
# 2. Agregar un mensaje de registro sin destruir lo existente
echo "Backup completado" >> logs_cron.log
# 3. Silenciar la salida exitosa pero guardar los fallos de red en un archivo de errores
curl -s "https://invalid.domain" > /dev/null 2> errores_red.log
# 4. Comportamiento silencioso completo para cronjobs
systemctl restart nginx &> /dev/null
7.2. Here-Docs (Documentos en Línea)
La sintaxis << te permite inyectar múltiples líneas de texto formateadas de forma directa en un comando o archivo:
# Escribir un archivo de configuración dinámico en una sola instrucción
cat <<EOF > /tmp/settings.conf
# Archivo de configuracion autogenerado
database_host="127.0.0.1"
database_port=5432
debug_mode=true
EOF
8. 🛡️ Depuración y Modo Seguro (Safe Bash)
Los scripts en Bash, por defecto, continúan su ejecución alegremente incluso si un comando intermedio falla estrepitosamente o si se lee una variable no definida. Para escribir código robusto que se detenga inmediatamente ante errores, implementa el Modo Seguro de Bash:
8.1. El Preámbulo de Oro (Safe Mode Declarations)
Coloca estas directivas inmediatamente debajo del shebang en todos tus scripts de producción:
#!/usr/bin/env bash
set -euo pipefail
¿Qué hace exactamente cada opción?
set -e(Exit on Error): Detiene inmediatamente la ejecución del script completo si algún comando finaliza con un código de retorno distinto de cero.- Excepción: No detiene el script si el error ocurre dentro de una estructura condicional (ej.
if,while).
- Excepción: No detiene el script si el error ocurre dentro de una estructura condicional (ej.
set -u(Nounset): Detiene la ejecución si el script intenta leer o usar una variable que no ha sido previamente definida o inicializada, evitando comportamientos erráticos con cadenas vacías no planificadas.set -o pipefail: Por defecto, un pipeline (ej.comando1 | comando2) solo devuelve el estado del último comando. Sicomando1falla, el script no se entera. Conpipefail, la tubería completa devuelve el código de error del comando que falló primero.
8.2. Depuración Activa de Scripts
Si un script se comporta de forma inesperada y necesitas ver exactamente qué valores toma cada variable e instrucción línea por línea, utiliza el modo debug:
# Activar la impresion detallada de cada instruccion ejecutada (Trace Mode)
set -x
# Instrucciones sospechosas...
mi_variable="valor_prueba"
echo "El valor es: $mi_variable"
# Desactivar la impresion detallada
set +x
9.
Expresiones Regulares y Procesamiento de Texto
Bash interactúa nativamente con las herramientas más potentes del ecosistema Unix para buscar, modificar e interpretar texto estructurado.
9.1. grep (Búsqueda de patrones)
Ideal para escanear y filtrar contenidos específicos:
# Buscar la palabra 'ERROR' de forma insensible a mayúsculas (-i) en un archivo
grep -i "error" /var/log/syslog
# Contar cuántas veces aparece una IP especifica (-c)
grep -c "192.168.1.50" /var/log/nginx/access.log
# Buscar de forma recursiva (-r) y listar solo nombres de archivo (-l) con coincidencia
grep -rl "DB_PASSWORD" ./src/
9.2. sed (Stream Editor - Reemplazo y Edición inplace)
Perfecto para alterar configuraciones al vuelo sin interacción humana:
# Reemplazar la primera ocurrencia de 'localhost' por '127.0.0.1' en un script
# (Salida por consola sin modificar el archivo original)
sed "s/localhost/127.0.0.1/" config.txt
# Reemplazo in-place (-i) de forma permanente en el archivo para todas las ocurrencias (/g)
sed -i "s/puerto_antiguo/puerto_nuevo/g" /etc/myapp/config.properties
9.3. awk (Procesador de Columnas y Reportes)
Excelente para parsear formatos tabulares o salidas de comandos:
# Imprimir solo la primera y tercera columna de un archivo delimitado por comas (-F)
awk -F',' '{print $1, $3}' usuarios.csv
# Filtrar procesos y mostrar solo el nombre y uso de memoria si superan un umbral
# awk extrae la columna 1 ($1) y la columna 4 ($4)
ps aux | awk '$4 > 5.0 {print "Proceso hambriento: " $1 " consume " $4 "%"}'
10. ⚠️ Errores Comunes y Trampas (Pitfalls)
Bash cuenta con múltiples “trampas de diseño” que provocan fallos sutiles pero devastadores en entornos de producción. Reconócelas y corrígelas antes de desplegar:
10.1. Olvidar Envolver Variables en Comillas Dobles
- Problema: Si una variable contiene espacios (ej.
nombre="Juan Carlos"), expandir$nombresin comillas dividirá el valor en múltiples argumentos para el comando siguiente, rompiendo la lógica. - Solución: Envuelve siempre la expansión de tus variables en comillas dobles.
# MALO
archivo="mi reporte final.pdf"
rm $archivo # Intentará borrar 'mi', 'reporte' y 'final.pdf' por separado.
# BUENO
rm "$archivo" # Trata el string completo con espacios como un unico argumento.
10.2. Colocar Espacios Alrededor de las Asignaciones
- Problema:
x = 5hace que Bash crea quexes un comando/programa ejecutable y=y5son sus argumentos. - Solución: Escribe la asignación de forma contigua.
# MALO: x = 10
# BUENO: x=10
10.3. Intentar Operaciones Matemáticas Decimales Directamente
- Problema: Bash solo maneja de forma nativa aritmética de enteros (
$(( 5 / 2 ))devolverá2, no2.5). - Solución: Utiliza herramientas externas especializadas como
bc(Basic Calculator) para cálculos decimales complejos.
# MALO (Solo calcula enteros)
resultado=$(( 5 / 2 )) # resultado = 2
# BUENO (Aritmética decimal de punto flotante de alta precisión con bc)
resultado_decimal=$(echo "scale=2; 5 / 2" | bc)
echo "El resultado exacto es: $resultado_decimal" # Imprime: 2.50
10.4. Iterar sobre la Salida de ls en un Bucle for
- Problema:
for f in $(ls *.txt)se romperá si algún archivo tiene espacios en el nombre, procesándolo como dos archivos individuales. - Solución: Utiliza comodines nativos del shell (Globbing), los cuales preservan los nombres de archivo intactos.
# MALO: for archivo in $(ls *.txt)
# BUENO (Seguro y recomendado):
for archivo in *.txt; do
[[ -e "$archivo" ]] || continue
echo "Procesando de forma segura: $archivo"
done
11.
Buenas Prácticas y Consejos Pro
- Usa siempre ShellCheck: Esta herramienta de análisis estático de código es obligatoria en cualquier flujo de trabajo serio en Bash. Te alertará de trampas de sintaxis, variables sin comillas y problemas de portabilidad al instante.
- Evita el anidamiento excesivo: Si tu script Bash requiere múltiples niveles de bucles y condicionales anidados, considera migrar esa sección lógica a Python o Go. Bash es ideal para orquestar herramientas, no para algoritmos densos de estructuras de datos.
- Nombre de archivos temporales controlados: Utiliza
mktempen combinación con la directivatrappara limpiar el sistema de archivos al terminar, incluso si el script finaliza por errores o señales del sistema (SIGINT,SIGTERM). - Escribe mensajes de error en
stderr: Los mensajes informativos de fallo deben enviarse al canal de error estándar2para no contaminar la salida de datos limpia del script (stdout).echo "Error critico: La conexion a la base de datos fallo." >&2
Este cheatsheet proporciona una referencia completa para el desarrollo de scripts robustos en Bash, cubriendo desde la sintaxis básica y los comparadores de control de flujo hasta las directivas avanzadas de depuración y las mejores prácticas del ecosistema moderno de ingeniería de software.