Nunca Construyas Comandos de Shell Como Cadenas
Estás a una variable sin comillas de borrar el directorio equivocado. Cuando tu agente de IA — o tu propio script — arma un comando como una cadena de texto, el shell tiene un segundo voto sobre lo que esa cadena significa. Un espacio suelto, una variable vacía o un nombre de archivo con un carácter de glob, y ejecuta algo que nunca escribiste.
Por qué las cadenas se parten
El shell no ejecuta la cadena que escribiste. Ejecuta la cadena después de la división en palabras (word splitting) y la expansión de rutas. Ambas ocurren en cada expansión sin comillas.
DIR="my project"
rm -rf $DIR # ejecuta: rm -rf my project → dos argumentos
rm -rf "$DIR" # ejecuta: rm -rf 'my project' → un argumento
Peor aún, si $DIR está vacía o sin definir, rm -rf $DIR/ se convierte en rm -rf /. El comando se ve correcto en tu editor y es catastrófico en tiempo de ejecución. Esto es el Bash Pitfall #1 y #2: el resultado de una expansión sigue sujeto a la división en palabras y a la expansión de rutas, así que siempre pones comillas dobles en las expansiones de parámetros.
Los tres hábitos
| Hábito | En vez de | Haz esto |
|---|---|---|
| Listas de argumentos | cmd="rm -rf $files"; $cmd | args=(-rf "$file"); rm "${args[@]}" |
| Cada expansión | grep $pat $file | grep "$pat" "$file" |
| Fallar rápido | (sin protección) | set -euo pipefail |
Usa arreglos para las listas de argumentos. Un arreglo de Bash mantiene cada elemento como una palabra distinta sin importar espacios o valores vacíos. "${args[@]}" se expande exactamente a los elementos que pusiste — sin volver a dividir, sin globbing:
flags=(--exclude "*.log" --exclude "tmp dir")
rsync "${flags[@]}" "$src" "$dst"
Pon comillas en cada expansión por defecto. "$var", "${arr[@]}", "$(cmd)". El caso raro en que sí quieres división en palabras es la excepción que comentas; las comillas son el valor por defecto en el que ni piensas.
Falla rápido, no destruyas el estado en silencio
Arranca los scripts con el trío estricto:
set -euo pipefail
-e(errexit) — aborta ante cualquier comando que salga con estado distinto de cero.-u(nounset) — trata una variable sin definir como error, de modo que un$DIRRmal escrito detiene el script en vez de expandirse a nada.-o pipefail— el estado de salida de una tubería es el del último comando que falló, no solo el de la última etapa, así quecurl … | tar …no reporta éxito cuandocurlmurió.
Juntos convierten un daño silencioso a medias en una salida temprana y ruidosa. set -u por sí solo habría atrapado el desastre de la $DIR vacía de arriba.
Notas para usuarios avanzados
set -etiene bordes filosos. Se suprime dentro de condicionales, en la mayoría de las etapas de una tubería y en las sustituciones de comandos (antes deinherit_errexit, Bash 4.4+). Es una red de seguridad, no un reemplazo de verificar los códigos de salida en los comandos que importan.pipefailinteractúa con head/tee. Puede hacer aparecer unSIGPIPEcuando un lector comoheadcierra antes de tiempo. Tenlo claro antes de depurar una falla "aleatoria".IFS=$'\n\t'es la cuarta línea del "modo estricto" común — quita el espacio del separador de campos para que la división accidental en palabras sea mucho menos destructiva.- ShellCheck atrapa esto de forma estática. Corre
shellcheck script.shen CI; SC2086 marca exactamente las expansiones sin comillas de las que trata este artículo, antes de que se ejecuten.
Nunca construyas comandos en cadenas. Arma las listas de argumentos como arreglos, pon comillas en todo y deja que el script falle ruidosamente en el instante en que algo esté mal.
Recursos
- Bash Pitfalls — Greg's Wiki (división en palabras, comillas, arreglos: pitfalls #1, #2, #14, #24)
- BashFAQ/105 — Why doesn't
set -edo what I expected? - The Set Builtin — Manual de GNU Bash (errexit, nounset, pipefail)
- Bash Arrays — Manual de GNU Bash
- ShellCheck SC2086 — comillas para evitar la división en palabras
¿Envías scripts que los agentes de IA escriben y ejecutan? Yeda AI audita y endurece la automatización de tu stack.