Referencing variables in a shell script

In a shell script, if I define a variable such as FOO=25, is there a difference between referencing it as $FOO$ and ${FOO}$?


Solution 1:

In most situations both $var and ${var} are the same. (Note that you must not use a $ at the end!)

One example where you need curly braces are when you need to put a variable in a continuous string.

Example:

var=hel
echo ${var}lo

will output hello.

Solution 2:

To answer your main question, ${foo} is called "parameter expansion". To be precise, the $ itself starts parameter expansion, the { } are actually optional according to the POSIX specification, but can be useful to indicate the end of the variable's name:

$ foo="bar"
$ echo $fooBaz   ## fails, no variable named $fooBaz exists

$ echo ${foo}Baz ## works, $foo is expanded and the string Baz is appended
barBaz

Basically, $foo and ${foo} are identical. Apart from cases like the above or when you are doing string manipulation, they are completely equivalent.

However, you shouldn't really use either of them. The rule of thumb is that, with very few exceptions, you should always use "$foo" or "${foo}" and never $foo or ${foo}. You should always quote your variables to avoid invoking the split+glob operator (more on that later). You certainly don't want $foo$. The final $ is irrelevant:

$ foo="bar"
$ echo "$foo$"
bar$

So, while unquoted variables are sometimes OK:

$ echo $foo
bar

They are usually not and should really be avoided:

$ if [ -n $foo ]; then echo empty; else echo "not empty"; fi ## fails
empty

$ if [ -n "$foo" ]; then echo empty; else echo "not empty"; fi ## works
not empty

Note that the brackets do not help here either:

$ if [ -n ${foo} ]; then echo empty; else echo "not empty"; fi  ## fails
empty

$ if [ -n "${foo}" ]; then echo empty; else echo "not empty"; fi ## works
not empty

When you use $foo or ${foo}, the shell will split the value saved in the variable on whitespace (this can be changed by setting the IFS variable to something else) into a list and then, each element of the list is treated as a glob pattern and expanded into any matching files or directories. This is known as the split+glob operator. To illustrate, consider a directory with two files:

$ ls -l
-rw-r--r-- 1 terdon terdon 0 Oct  9 18:16 file1
-rw-r--r-- 1 terdon terdon 0 Oct  9 18:16 file2

Now, let's set a variable to foo *:

$ foo="foo *"

What happens if we try to check if a file with that name exists?

$ if [ -e $foo ]; then echo "file exists"; else echo "no such file"; fi
file exists

The variable was split into foo and * and, since * is a wildcard that matches any string, the shell tells you that a file called foo * exxists. However, if we quote it correctly, this doesn't happen:

$ if [ -e "$foo" ]; then echo "file exists"; else echo "no such file"; fi
no such file

That was a trivial example to illustrate the point. Imagine if I had used rm instead of echo though.

So, first rule: always quote your variables. You can use either "$foo" or "${foo}" but quote it either way. For more detail on using variables safely, have a look at these posts:

  • $VAR vs ${VAR} and to quote or not to quote
  • Expansion of a shell variable and effect of glob and split on it
  • Security implications of forgetting to quote a variable in bash/POSIX shells
  • Why do I need to quote variable for if, but not for echo?