Here are a series of cases where echo $var
can show a different value than what was just assigned. This happens regardless of whether the assigned value was "double quoted", 'single quoted' or unquoted.
How do I get the shell to set my variable correctly?
Asterisks
The expected output is /* Foobar is free software */
, but instead I get a list of filenames:
$ var="/* Foobar is free software */"
$ echo $var
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...
Square brackets
The expected value is [a-z]
, but sometimes I get a single letter instead!
$ var=[a-z]
$ echo $var
c
Line feeds (newlines)
The expected value is a a list of separate lines, but instead all the values are on one line!
$ cat file
foo
bar
baz
$ var=$(cat file)
$ echo $var
foo bar baz
Multiple spaces
I expected a carefully aligned table header, but instead multiple spaces either disappear or are collapsed into one!
$ var=" title | count"
$ echo $var
title | count
Tabs
I expected two tab separated values, but instead I get two space separated values!
$ var=$'key\tvalue'
$ echo $var
key value
In all of the cases above, the variable is correctly set, but not correctly read! The right way is to use double quotes when referencing:
This gives the expected value in all the examples given. Always quote variable references!
Why?
When a variable is unquoted, it will:
Undergo field splitting where the value is split into multiple words on whitespace (by default):
Before:
/* Foobar is free software */
After:
/*
,Foobar
,is
,free
,software
,*/
Each of these words will undergo pathname expansion, where patterns are expanded into matching files:
Before:
/*
After:
/bin
,/boot
,/dev
,/etc
,/home
, ...Finally, all the arguments are passed to echo, which writes them out separated by single spaces, giving
instead of the variable's value.
When the variable is quoted it will:
This is why you should always quote all variable references, unless you specifically require word splitting and pathname expansion. Tools like shellcheck are there to help, and will warn about missing quotes in all the cases above.
Additional to putting the variable in quotation, one could also translate the output of the variable using
tr
and converting spaces to newlines.Although this is a little more convoluted, it does add more diversity with the output as you can substitute any character as the separator between array variables.
You may want to know why this is happening. Together with the great explanation by that other guy, find a reference of Why does my shell script choke on whitespace or other special characters? written by Gilles in Unix & Linux:
user double quote to get the exact value. like this:
and it will read your value correctly.
echo $var
output highly depends on the value ofIFS
variable. By default it contains space, tab, and newline characters:This means that when shell is doing field splitting (or word splitting) it uses all these characters as word separators. This is what happens when referencing a variable without double quotes to echo it (
$var
) and thus expected output is altered.One way to prevent word splitting (besides using double quotes) is to set
IFS
to null. See http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 :Setting to null means setting to empty value:
Test:
In addition to other issues caused by failing to quote,
-n
and-e
can be consumed byecho
as arguments. (Only the former is legal per the POSIX spec forecho
, but several common implementations violate the spec and consume-e
as well).To avoid this, use
printf
instead ofecho
when details matter.Thus:
However, correct quoting won't always save you when using
echo
:...whereas it will save you with
printf
: