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

Aquí hay una serie de casos donde echo $var puede mostrar un valor diferente al que se acaba de asignar. Esto sucede independientemente de si el valor asignado fue “comillas dobles”, “comillas simples” o sin comillas.

¿Cómo obtengo que el shell establezca mi variable correctamente?

Asteriscos

El resultado esperado es /* Foobar is free software */ , pero en su lugar obtengo una lista de nombres de archivo:

 $ 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 [az] , pero a veces ¡recibo una sola letra!

 $ var=[az] $ echo $var c 

Line feeds (líneas nuevas)

El valor esperado es una lista de líneas separadas, ¡pero en cambio todos los valores están en una 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 su lugar, ¡múltiples espacios desaparecen o se colapsan en uno!

 $ var=" title | count" $ echo $var title | count 

Pestañas

¡Esperaba dos valores separados por tabuladores, pero en cambio 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 correctamente leída! La forma correcta es usar comillas dobles al hacer referencia a :

 echo "$var" 

Esto le da el valor esperado en todos los ejemplos dados. ¡Siempre cita referencias variables!


¿Por qué?

Cuando una variable no está citada , será:

  1. Someterse a la división de campos donde el valor se divide en múltiples palabras en espacios en blanco (por defecto):

    Antes: /* Foobar is free software */

    Después: /* , Foobar , is , software free , */

  2. Cada una de estas palabras se someterá a la expansión del 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 al eco, que los escribe separados por espacios individuales , 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

Esta es la razón por la que siempre debe citar todas las referencias de variables , a menos que requiera específicamente la división de palabras y la expansión del nombre de ruta. Herramientas como shellcheck están ahí para ayudar, y advertirán acerca de las comillas faltantes en todos los casos anteriores.

Es posible que desee saber por qué está sucediendo esto. Junto con la gran explicación de ese otro tipo , encuentra una referencia de ¿Por qué mi script de shell se atraganta con espacios en blanco u otros caracteres especiales? escrito por Gilles en Unix y Linux :

¿Por qué tengo que escribir "$foo" ? ¿Qué pasa sin las comillas?

$foo no 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 construya 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 nombre de archivo: trate cada campo como un comodín, es decir, como un patrón comodín, y reemplácelo por la lista de nombres de archivos que coinciden 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 , seguido por la lista de archivos en el directorio actual y finalmente la 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 syntax de shell: contexto de lista y contexto de cadena. La división de campos y la generación de nombres de archivo solo ocurren en el contexto de la lista, pero eso ocurre la mayor parte del tiempo. Las comillas dobles delimitan un contexto de cadena: toda la cadena de comillas dobles es una sola cadena, que no debe dividirse. (Excepción: "$@" para 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 sucede con la sustitución de comandos con $(foo) o con `foo` . En una nota lateral, no use `foo` : sus reglas de cita son extrañas y no portátiles, y todas las shells modernas admiten $(foo) que es absolutamente equivalente, excepto por tener reglas de cita intuitivas.

El resultado de la sustitución aritmética también se somete a las mismas expansiones, pero eso normalmente no es una preocupación ya que solo contiene caracteres no expandibles (suponiendo que IFS no contiene dígitos o - ).

Ver ¿ Cuándo es doble la cita necesaria? para obtener más detalles sobre los casos en los que puede omitir las comillas.

A menos que quiera decir que todo esto va a suceder, solo recuerde usar siempre comillas dobles para las sustituciones de variables y comandos. Tenga cuidado: omitir las comillas puede conducir no solo a errores sino a agujeros de seguridad .

echo $var salida echo $var depende en gran medida del valor de la variable IFS . Por defecto, contiene espacios, tabs y caracteres de nueva línea:

 [ks@localhost ~]$ echo -n "$IFS" | cat -vte ^I$ 

Esto significa que cuando shell realiza división de palabras (o división de palabras) utiliza todos estos caracteres como separadores de palabras. Esto es lo que sucede cuando se hace referencia a una variable sin comillas dobles para hacer eco de ella ( $var ) y, por lo tanto, la producción esperada se altera.

Una forma de evitar la división de palabras (además de usar comillas dobles) es establecer IFS en nulo. Ver http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 :

Si el valor de IFS es nulo, no se realizará ninguna división de campo.

Establecer en nulo significa configurar el valor vacío:

 IFS= 

Prueba:

 [ks@localhost ~]$ echo -n "$IFS" | cat -vte ^I$ [ks@localhost ~]$ var=$'key\nvalue' [ks@localhost ~]$ echo $var key value [ks@localhost ~]$ IFS= [ks@localhost ~]$ echo $var key value [ks@localhost ~]$ 

doble cita del usuario para obtener el valor exacto. Me gusta esto:

 echo "${var}" 

y leerá tu valor correctamente.

Además de poner la variable entre comillas, también se podría traducir el resultado de la variable usando tr y convirtiendo espacios en nuevas líneas.

 $ echo $var | tr " " "\n" foo bar baz 

Aunque esto es un poco más intrincado, agrega más diversidad con la salida, ya que puede sustituir cualquier carácter como el separador entre las variables de la matriz.