Acabo de asignar una variable, pero echo $variable muestra algo más
A continuación se muestran una serie de casos en los que echo $var
puede mostrar un valor diferente al que se acaba de asignar. Esto sucede independientemente de si el valor asignado estaba entre comillas dobles, entre comillas simples o sin comillas.
¿Cómo consigo que el shell configure mi variable correctamente?
asteriscos
El resultado esperado es /* Foobar is free software */
, pero en su lugar obtengo una lista de nombres de archivos:
$ var="/* Foobar is free software */"
$ echo $var
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...
Corchetes
El valor esperado es [a-z]
, ¡pero a veces obtengo una sola letra!
$ var=[a-z]
$ echo $var
c
Avances de línea (nuevas líneas)
El valor esperado es una lista de líneas separadas, ¡pero todos los valores están en una sola línea!
$ cat file
foo
bar
baz
$ var=$(cat file)
$ echo $var
foo bar baz
Múltiples espacios
Esperaba un encabezado de tabla cuidadosamente alineado, ¡pero en lugar de eso, varios espacios desaparecen o se colapsan en uno!
$ var=" title | count"
$ echo $var
title | count
Pestañas
Esperaba dos valores separados por tabulaciones, ¡pero en lugar de eso obtengo dos valores separados por espacios!
$ var=$'key\tvalue'
$ echo $var
key value
En todos los casos anteriores, la variable está configurada correctamente, ¡pero no se lee correctamente! La forma correcta es utilizar comillas dobles al hacer referencia :
echo "$var"
Esto da el valor esperado en todos los ejemplos dados. ¡Cite siempre referencias variables!
¿Por qué?
Cuando una variable no está entrecomillada ,:
Realizar una división de campos donde el valor se divide en varias palabras en espacios en blanco (de forma predeterminada):
Antes:
/* Foobar is free software */
Después:
/*
,Foobar
,is
,free
,software
,*/
Cada una de estas palabras se someterá a una expansión de nombre de ruta , donde los patrones se expanden en archivos coincidentes:
Antes:
/*
Después:
/bin
,/boot
,/dev
,/etc
,/home
, ...Finalmente, todos los argumentos se pasan a echo, que los escribe separados por espacios simples , dando
/bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/
en lugar del valor de la variable.
Cuando se cita la variable :
- Ser sustituido por su valor.
- No hay paso 2.
Es por eso que siempre debe citar todas las referencias de variables , a menos que requiera específicamente la división de palabras y la expansión de la ruta. Herramientas como shellcheck están ahí para ayudar y advertirán sobre comillas faltantes en todos los casos anteriores.
Quizás quieras saber por qué sucede esto. Junto con la gran explicación de ese otro tipo , busque una referencia de ¿Por qué mi script de shell se ahoga con espacios en blanco u otros caracteres especiales? escrito por Gilles en Unix y Linux :
¿Por qué necesito escribir
"$foo"
? ¿Qué pasa sin las comillas?
$foo
no significa “tomar el valor de la variablefoo
”. Significa algo mucho más complejo:
- Primero, toma el valor de la variable.
- División de campos: trate ese valor como una lista de campos separados por espacios en blanco y cree la lista resultante. Por ejemplo, si la variable contiene
foo * bar
, el resultado de este paso es la lista de 3 elementosfoo
,*
,bar
.- Generación de nombres de archivos: trate cada campo como un global, es decir, como un patrón comodín, y reemplácelo por la lista de nombres de archivos que coincidan con este patrón. Si el patrón no coincide con ningún archivo, no se modifica. En nuestro ejemplo, esto da como resultado la lista que contiene
foo
, seguida de la lista de archivos en el directorio actual y, finalmentebar
, . Si el directorio actual está vacío, el resultado esfoo
,*
,bar
.Tenga en cuenta que el resultado es una lista de cadenas. Hay dos contextos en la sintaxis del shell: contexto de lista y contexto de cadena. La división de campos y la generación de nombres de archivos solo ocurren en el contexto de la lista, pero eso ocurre la mayor parte del tiempo. Las comillas dobles delimitan el contexto de una cadena: toda la cadena entre comillas dobles es una cadena única que no debe dividirse. (Excepción:
"$@"
expandir a la lista de parámetros posicionales, por ejemplo,"$@"
es equivalente a"$1" "$2" "$3"
si hay tres parámetros posicionales. Consulte ¿Cuál es la diferencia entre $* y $@? )Lo mismo ocurre con la sustitución de comandos con
$(foo)
o con`foo`
. Como nota al margen, no lo use`foo`
: sus reglas de cotización son extrañas y no portátiles, y todos los shells modernos admiten$(foo)
lo cual es absolutamente equivalente, excepto por tener reglas de cotización intuitivas.La salida de la sustitución aritmética también sufre las mismas expansiones, pero eso normalmente no es una preocupación ya que solo contiene caracteres no expandibles (suponiendo que
IFS
no contenga dígitos o-
).Consulte ¿Cuándo son necesarias las comillas dobles? para obtener más detalles sobre los casos en los que puede omitir las comillas.
A menos que quieras que suceda todo este galimatías, recuerda usar siempre comillas dobles alrededor de las sustituciones de variables y comandos. Tenga cuidado: omitir las comillas puede provocar no sólo errores sino también agujeros de seguridad .
usuario comilla doble para obtener el valor exacto. como esto:
echo "${var}"
y leerá su valor correctamente.
Además de otros problemas causados por no citar, -n
pueden -e
consumirse echo
como argumentos. (Solo el primero es legal según la especificación POSIX para echo
, pero varias implementaciones comunes violan la especificación y -e
también consumen).
Para evitar esto, utilice printf
en lugar de echo
cuando los detalles importen.
De este modo:
$ vars="-e -n -a"
$ echo $vars # breaks because -e and -n can be treated as arguments to echo
-a
$ echo "$vars"
-e -n -a
Sin embargo, las citas correctas no siempre le salvarán cuando utilice echo
:
$ vars="-n"
$ echo "$vars"
$ ## not even an empty line was printed
...mientras que te salvará con printf
:
$ vars="-n"
$ printf '%s\n' "$vars"
-n