Acabo de asignar una variable, pero echo $variable muestra algo más

Resuelto that other guy asked hace 9 años • 7 respuestas

A continuación se muestran una serie de casos en los que echo $varpuede 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
that other guy avatar Apr 01 '15 04:04 that other guy
Aceptado

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 ,:

  1. 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,*/

  2. 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, ...

  3. 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 :

  1. Ser sustituido por su valor.
  2. 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.

that other guy avatar Mar 31 '2015 21:03 that other guy

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?

$foono significa “tomar el valor de la variable foo”. 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 elementos foo, *, 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, finalmente bar, . Si el directorio actual está vacío, el resultado es foo, *, 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 IFSno 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 .

fedorqui avatar Jul 09 '2015 14:07 fedorqui

usuario comilla doble para obtener el valor exacto. como esto:

echo "${var}"

y leerá su valor correctamente.

vanishedzhou avatar Aug 03 '2018 04:08 vanishedzhou

Además de otros problemas causados ​​por no citar, -npueden -econsumirse echocomo argumentos. (Solo el primero es legal según la especificación POSIX para echo, pero varias implementaciones comunes violan la especificación y -etambién consumen).

Para evitar esto, utilice printfen lugar de echocuando 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
Charles Duffy avatar Sep 21 '2018 22:09 Charles Duffy