Errores comunes de scripting en Bash y cómo evitarlos
Evita errores comunes en scripts Bash con manejo seguro de errores, comillas, arrays, trampas y análisis de argumentos.
Errores comunes en scripts Bash y cómo evitarlos
Los errores en scripts Bash suelen aparecer cuando tu script se encuentra con nombres de archivos reales, variables faltantes, comandos fallidos o entrada inesperada. Un script que funciona en tu portátil puede romperse en CI o producción si depende de valores predeterminados laxos.
No necesitas hacer que cada script de shell sea complicado. Sí necesitas poner entre comillas las expansiones, verificar fallos intencionalmente y probar con nombres que contengan espacios.
Establece valores predeterminados más seguros con cuidado
Muchos scripts comienzan con:
#!/usr/bin/env bash
set -euo pipefail
Esa es una buena base para muchos scripts de automatización, pero cada opción tiene sus aristas:
set -esale cuando un comando simple falla, excepto en lugares como pruebasif, partes de listas&&y||, y algunas sustituciones de comandos.set -usale cuando expandes una variable no establecida.set -o pipefailhace que una tubería falle si algún comando en la tubería falla, no solo el último comando.
Usa estas opciones cuando fallar temprano sea más seguro que continuar. Para comandos donde se espera un fallo, maneja el estado explícitamente.
if ! grep -q "ready" status.txt; then
echo "el servicio aún no está listo"
exit 1
fi
Pon entre comillas las expansiones de variables
Las variables sin comillas son el error más común en Bash. Bash realiza división de palabras y expansión de glob en expansiones sin comillas, por lo que una ruta como release notes/*.txt puede convertirse en varios argumentos o coincidir con archivos que no pretendías.
archivo="release notes.txt"
# Mal: se rompe porque el valor se divide en dos palabras.
rm $archivo
# Bien: pasa un argumento exacto.
rm -- "$archivo"
Usa -- antes de nombres de archivo controlados por el usuario cuando un comando lo soporte. Esto evita que un nombre de archivo como -rf sea interpretado como una opción.
Usa arrays para listas de argumentos
No almacenes un comando con argumentos en una sola cadena y luego lo ejecutes. Las comillas se vuelven frágiles rápidamente.
# Mal
banderas="-a --exclude node_modules"
rsync $banderas "$origen" "$destino"
# Bien
banderas=(-a --exclude "node_modules")
rsync "${banderas[@]}" "$origen" "$destino"
Los arrays preservan los límites de los argumentos. Esto importa cuando un argumento contiene espacios, caracteres comodín o valores que comienzan con un guión.
Prefiere $(...) sobre las comillas invertidas
Las comillas invertidas son difíciles de anidar y fáciles de leer mal. Usa $(...) para la sustitución de comandos.
rama_actual="$(git rev-parse --abbrev-ref HEAD)"
echo "construyendo rama: $rama_actual"
Mantén las sustituciones de comandos entre comillas a menos que deliberadamente quieras división de palabras.
Lee archivos sin perder datos
Este patrón parece inofensivo pero se rompe con espacios y puede distorsionar las barras invertidas:
for linea in $(cat hosts.txt); do
echo "$linea"
done
Lee archivos con while IFS= read -r en su lugar.
while IFS= read -r host; do
echo "verificando $host"
done < hosts.txt
IFS= preserva espacios en blanco al inicio y al final. -r evita que se interpreten los escapes de barra invertida.
Maneja archivos temporales con mktemp y trap
Las rutas temporales codificadas pueden colisionar con otro proceso o dejar archivos obsoletos. Crea una ruta única y límpiala al salir.
archivo_tmp="$(mktemp)"
limpiar() {
rm -f "$archivo_tmp"
}
trap limpiar EXIT
printf '%s\n' "datos de trabajo" > "$archivo_tmp"
Para directorios, usa mktemp -d y elimina el directorio en tu función de limpieza.
Analiza opciones con getopts
El análisis manual de argumentos a menudo pasa por alto casos límite. Para opciones cortas, el getopts integrado de Bash suele ser suficiente.
verboso=false
salida=""
while getopts ":vo:" opt; do
case "$opt" in
v) verboso=true ;;
o) salida="$OPTARG" ;;
:)
echo "La opción -$OPTARG requiere un argumento" >&2
exit 2
;;
\?)
echo "Opción desconocida: -$OPTARG" >&2
exit 2
;;
esac
done
shift "$((OPTIND - 1))"
getopts maneja banderas cortas como -v y -o archivo. Si tu script necesita opciones largas como --output, escribe un analizador cuidadoso o usa un lenguaje con una biblioteca de análisis de argumentos más robusta.
Verifica comandos que pueden fallar
No asumas que un comando funcionó porque imprimió algo. Verifica operaciones importantes antes de usar su salida.
if ! archivo="$(tar -czf app.tar.gz app 2>&1)"; then
echo "el archivo falló: $archivo" >&2
exit 1
fi
Para tuberías, habilita pipefail cuando un fallo en medio deba fallar toda la tubería.
set -o pipefail
journalctl -u api.service | grep -i "error"
Sin pipefail, el estado de la tubería normalmente proviene del último comando.
Evita Bash cuando la portabilidad importe
Si tu script usa arrays, [[ ... ]], mapfile o pipefail, es un script de Bash. Comienza con:
#!/usr/bin/env bash
Si necesitas portabilidad POSIX sh, evita características exclusivas de Bash y prueba con el shell que usa tu sistema objetivo. No escribas un script de Bash con #!/bin/sh y esperes que se comporte igual en todas partes.
Conclusión
La forma más rápida de mejorar tus scripts de Bash es probarlos con entrada desordenada: espacios en nombres de archivo, variables faltantes, archivos vacíos y comandos fallidos. Pon entre comillas las expansiones, usa arrays para listas de argumentos, limpia archivos temporales con trap y haz explícitos los caminos de fallo. Tu yo futuro pasará menos tiempo depurando scripts que solo funcionaban con entrada perfecta.