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:
- Sustituye las variables por su valor:
$nombre→Nica - Ejecuta las sustituciones de comandos:
$(pwd)→/home/nica - Parte el resultado en palabras por los espacios (word splitting)
- Expande los comodines:
*.txt→notas.txt informe.txt - 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 -xdelante: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 nadaEn 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,mvycp. 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"
fiEse 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 ]; thenY [ -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"
fiLa 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 espaciosLas 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.txtSi 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
Crea una variable
saludocon el valorHola desde Bashy muéstrala conechode las tres formas: sin comillas, con dobles y con simples. Explica qué diferencia ves y cuál no se aprecia en pantalla.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.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.Usa
$(...)para guardar en una variable el número de archivos del directorio actual y muéstralo en una frase.Crea una variable
basecon el valorcopiay consigue queechoimprimacopia_seguridad.txtusando llaves.Haz que
echoimprima literalmenteEl total es $100de dos maneras distintas: con comillas simples y con\.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.