bash - hello - run script linux
Acabo de asignar una variable, pero echo $ variable muestra algo más (6)
Aquí hay 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 fue "comillas dobles", "comillas simples" o sin comillas.
¿Cómo consigo que el shell establezca mi variable correctamente?
Asteriscos
El resultado esperado es
/* Foobar is free software */
, pero en cambio 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=[a-z]
$ echo $var
c
Saltos de línea (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
Además de otros problemas causados por la falta de comillas,
-n
y
-e
pueden ser consumidos por
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 también consumen
-e
).
Para evitar esto,
use
printf
lugar de
echo
cuando los detalles importen.
Así:
$ 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 te salvarán cuando uses
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
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 líneas nuevas.
$ echo $var | tr " " "/n"
foo
bar
baz
Aunque esto es un poco más complicado, agrega más diversidad con la salida, ya que puede sustituir cualquier carácter como separador entre las variables de la matriz.
En todos los casos anteriores, la variable se establece correctamente, ¡pero no se lee correctamente! La forma correcta es usar comillas dobles al hacer referencia a :
echo "$var"
Esto da el valor esperado en todos los ejemplos dados. ¡Siempre cita referencias variables!
¿Por qué?
Cuando una variable no está entre comillas , hará lo siguiente:
-
Someterse a la división de campos donde el valor se divide en varias palabras en espacios en blanco (por defecto):
Antes:
/* Foobar is free software */
Después:
/*
,Foobar
,is
,free
,software
,*/
-
Cada una de estas palabras sufrirá 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 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:
- 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 del nombre de ruta. Las herramientas como shellcheck están ahí para ayudar y advertirán sobre las citas faltantes en todos los casos anteriores.
Es posible que desee saber por qué sucede esto. Junto con la gran explicación de ese otro tipo , encuentre una referencia de ¿Por qué mi script de shell se ahoga en 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, tome 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 nombre de archivo: trate cada campo como un globo, es decir, como un patrón comodín, y reemplácelo por la lista de nombres de archivo 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
, 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 de shell: contexto de lista y contexto de cadena. La división de campos y la generación de nombre de archivo solo ocurren en el contexto de la lista, pero eso es la mayor parte del tiempo. Las comillas dobles delimitan un contexto de cadena: la cadena completa entre comillas dobles es una sola cadena, que no se debe dividir. (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`
. Como nota al margen, no use`foo`
: sus reglas de cotización son extrañas y no portátiles, y todos los shells modernos admiten$(foo)
que es absolutamente equivalente, excepto por tener reglas de cotización intuitivas.La salida de la sustitución aritmética también experimenta 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-
).Ver ¿ Cuándo es necesaria la doble cita? para obtener más detalles sobre los casos en que puede omitir las citas.
A menos que quiera que suceda todo este rigmarole, solo recuerde usar siempre comillas dobles alrededor de las sustituciones de variables y comandos. Tenga cuidado: omitir las comillas puede conducir no solo a errores, sino también a agujeros de seguridad .
cita doble del usuario para obtener el valor exacto. Me gusta esto:
echo "${var}"
y leerá su valor correctamente.
echo $var
salida
echo $var
depende en gran medida del valor de la variable
IFS
.
Por defecto contiene espacio, tabulación y caracteres de nueva línea:
[ks@localhost ~]$ echo -n "$IFS" | cat -vte
^I$
Esto significa que cuando Shell realiza la división de campos (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 repetirla (
$var
) y, por lo tanto, se altera la salida esperada.
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á división de campo.
Establecer en nulo significa establecer en 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 ~]$