detomas.net Curso de Bash desde cero
Descargar PDF

Curso de Bash desde cero / Capítulo 9

Comillas y expansión

Este es el capítulo más importante del curso, y el que casi nadie explica a tiempo. La mayoría de los scripts que fallan de forma rara —que funcionan con un archivo pero no con otro, o que se rompen cuando algo está vacío— fallan por lo que se cuenta aquí.

La idea de fondo es sencilla: antes de ejecutar nada, Bash reescribe la línea que has escrito. Ese paso intermedio se llama expansión, y si no sabes que existe, el comportamiento de Bash parece caprichoso.

Qué hace Bash antes de ejecutar

Cuando pulsas Enter, Bash no ejecuta tu línea tal cual. Primero la transforma, en este orden:

  1. Sustituye las variables por su valor: $nombreNica
  2. Ejecuta las sustituciones de comandos: $(pwd)/home/nica
  3. Parte el resultado en palabras por los espacios (word splitting)
  4. Expande los comodines: *.txtnotas.txt informe.txt
  5. Y entonces ejecuta el comando con las palabras resultantes

Los pasos 3 y 4 son los que dan sorpresas, porque ocurren después de meter el valor de la variable. Bash no ve $nombre: ve lo que había dentro.

Consejo

Cuando no entiendas qué está haciendo un script, ponle bash -x delante: bash -x script.sh. Te muestra cada línea ya expandida, justo antes de ejecutarla. Es la mejor forma de ver este capítulo en acción.

Los tres tipos de comillas

Bash tiene tres formas de escribir texto, y hacen cosas distintas.

Forma Qué hace con $variable Qué hace con los espacios
Sin comillas La sustituye Parte el texto en palabras
"comillas dobles" La sustituye Los respeta: es una sola palabra
'comillas simples' No la sustituye, es texto literal Los respeta

Un ejemplo que lo enseña todo de golpe:

nombre="Nica de Tomás"

echo $nombre     # Nica de Tomás   -> tres palabras
echo "$nombre"   # Nica de Tomás   -> una sola palabra
echo '$nombre'   # $nombre         -> literal, no sustituye nada

En pantalla las dos primeras se ven casi igual, y por eso el error pasa desapercibido. Pero para Bash son cosas muy distintas: en la primera, echo recibe tres argumentos; en la segunda, uno.

Con echo da igual. Con casi todo lo demás, no.

El problema de verdad: nombres con espacios

Imagina que tienes un archivo llamado mis notas.txt, con un espacio:

archivo="mis notas.txt"

rm $archivo      # MAL: Bash lo lee como  rm "mis" "notas.txt"
                 # Intenta borrar DOS archivos que no existen

rm "$archivo"    # BIEN: borra el archivo "mis notas.txt"

Sin comillas, Bash parte el valor por el espacio y rm recibe dos argumentos. Con comillas, recibe uno. El script “funciona” mientras tus archivos no tengan espacios, y se rompe el día que alguno los tiene.

Precaución

Este fallo es especialmente peligroso con rm, mv y cp. Un script sin comillas puede borrar algo que no querías, o quejarse de archivos que sí existen.

El problema silencioso: variables vacías

Este es peor, porque no da error: da un resultado falso.

nombre=""

if [ -n $nombre ]; then
    echo "El nombre existe"
fi

Ese script imprime «El nombre existe», aunque el nombre esté vacío. ¿Por qué? Porque Bash sustituye $nombre por nada, y la línea que se ejecuta de verdad es:

if [ -n ]; then

Y [ -n ] con un solo argumento no comprueba nada: [ simplemente ve un texto (-n) que no está vacío y responde «verdadero». La comprobación se ha evaporado.

Con comillas funciona como esperas:

nombre=""

if [ -n "$nombre" ]; then     # se convierte en:  [ -n "" ]  -> falso
    echo "El nombre existe"
fi

La regla práctica

No hace falta memorizar los casos. Basta con una norma:

Importante

Pon siempre comillas dobles alrededor de las variables: "$archivo", "$nombre", "$1", "$@".

Es más fácil ponerlas siempre que acordarse de cuándo hacen falta. No estorban nunca, y te ahorran la clase entera de errores de este capítulo.

Usa comillas simples solo cuando quieras que el texto salga tal cual, sin que Bash toque nada:

echo 'El precio es $50'          # El precio es $50
echo "El precio es $50"          # El precio es 0   <- ¡ojo!

En el segundo caso Bash intentó sustituir la variable $5 (que está vacía) y dejó el 0 suelto.

Sustitución de comandos con $(…)

Sirve para meter la salida de un comando dentro de otra cosa:

echo "Hoy es $(date '+%d/%m/%Y')"
echo "Estás en $(pwd)"

archivos=$(ls *.txt | wc -l)
echo "Hay $archivos archivos de texto"

Verás también la forma antigua con comillas invertidas, `pwd`. Hace lo mismo, pero usa siempre $(...): se puede anidar y se distingue mucho mejor de las comillas normales.

Y aquí también hacen falta las comillas dobles, por lo mismo de antes:

ruta=$(pwd)
cd "$ruta"      # funciona aunque la ruta tenga espacios

Las llaves ${variable}

Las llaves marcan dónde acaba el nombre de la variable. Sin ellas, Bash no sabe dónde cortar:

fichero="informe"

echo "$fichero_final.txt"     # .txt   -> busca la variable "fichero_final",
                              #           que no existe, y solo queda el .txt
echo "${fichero}_final.txt"   # informe_final.txt

Si detrás de la variable viene una letra, un número o un guion bajo, necesitas las llaves. En el resto de casos son opcionales.

Escapar caracteres con la barra invertida

La barra invertida quita el significado especial al carácter siguiente:

echo "Cuesta \$50"           # Cuesta $50   (el \ evita la sustitución)
echo "Comillas: \"hola\""    # Comillas: "hola"
touch mi\ archivo.txt        # crea un archivo llamado "mi archivo.txt"

Resumen

Escribes Bash entiende
$var El valor, partido por espacios y con comodines expandidos
"$var" El valor, entero, como una sola palabra
'$var' El texto literal $var
${var} El valor, marcando dónde acaba el nombre
$(comando) La salida del comando
\$ Un símbolo de dólar normal y corriente

Ejercicios

  1. Crea una variable saludo con el valor Hola desde Bash y muéstrala con echo de las tres formas: sin comillas, con dobles y con simples. Explica qué diferencia ves y cuál no se aprecia en pantalla.

  2. Crea un archivo con un espacio en el nombre (touch "mi archivo.txt"). Guarda el nombre en una variable e intenta borrarlo sin comillas. ¿Qué error da? Ahora hazlo con comillas.

  3. Escribe un script que reciba un argumento y compruebe con [ -n ... ] si está vacío. Pruébalo primero sin comillas y ejecútalo sin pasarle nada. ¿Qué responde? Añade las comillas y vuelve a probarlo.

  4. Usa $(...) para guardar en una variable el número de archivos del directorio actual y muéstralo en una frase.

  5. Crea una variable base con el valor copia y consigue que echo imprima copia_seguridad.txt usando llaves.

  6. Haz que echo imprima literalmente El total es $100 de dos maneras distintas: con comillas simples y con \.

  7. Coge cualquier script de un capítulo anterior y ejecútalo con bash -x. Localiza en la salida una línea donde se vea una variable ya sustituida.