6.3 Bash Variables
On to our Bash example.
1cd ~/bash 2shellspec
1Running: /bin/sh [sh] 2. 3 4Finished in 0.04 seconds (user 0.04 seconds, sys 0.00 seconds) 51 example, 0 failures
Everything passing our tests. Edit the program file.
1vi bin/convert
Change its content to the following.
1greeting_message="Hello World!" 2echo "${greeting_message}"
Wow! Line 2 is very different in Bash. Not only do we use echo but the reference to the variable ${greeting_message}
is more complex.
Let’s unpack line 2. echo is the instruction to output to the screen. Next we have what looks like a string literal (just like when we had "Hello World!" in this position) but it’s not. This string contains a variable reference ${greeting_message}
. This requires some examination.
In general, in Bash, we define variables using their name (label) and refer to the value assigned to a variable by adding the $
prefix. So, greeting_message="Hello World!"
defines variable greeting_message
to have the value "Hello World!" (the literal string ’Hello World!’) and $greeting_message
recalls the value of greeting_message
.
In this case, we could have written the program as follows.
1greeting_message="Hello World!" 2echo $greeting_message
Try it. If you change the program and run the tests you will find it all still passes.
The echo
command in Bash treats everything it sees as a string of characters to be output to the screen.
When run, the line echo $greeting_message
is first expanded by replacing $greeting_message
with its value Hello World!. Notice that the "" characters are not
part of the value of greeting_message
they are only used to delimit a string (tell Bash that whatever is between them is the value to be assigned to greeting_message
). After this expansion the line becomes echo Hello World!
and echo
takes anything following (Echo World!) as the string to be output.
Why then all the additional "{}"
characters in the original version of the script?
6.3.1 Separating variable names
Suppose I want to print an X immediately after the greeting_message? I might try echo $greeting_messageX
but this will fail because we are now referencing a variable called greeting_messageX, which is
undefined in this program so Bash replaces the reference with nothing and echo
prints nothing1. Having failed, we might try echo $greeting_message X
but this outputs the greeting_message, a space, and then an X. Not what we want. Finally, we can use the special syntax surrounding the variable name with {}
. This allows us to place the X immediately after the variable echo ${greeting_message}X
, getting us what we want (the message Hello World!X, with the X immediately after
our message).
That explains the {}
characters, but why surround the variable with ""
characters? We do this to prevent two, fairly subtle, causes of bugs in Bash scripts; ‘globbing’ and ‘word splitting’.
6.3.2 Globbing and word splitting
‘Globbing’ is a feature of Bash that replaces certain characters on a line with entries from the file system. If, for example, you enter the command echo *
on the Bash command line you will not see a * output. Instead you will see a list of files and directories (in fact the same list as you would see by typing ls .
). This is because of ‘globbing’. Before a line is executed (either on the command line or in a script) the *
is expanded to a space separated list of files and directories that match the wildcard pattern *
. The problem with this is that if a variable contained one of these globbing patterns then it will be expanded. Try the following at the Bash command line.
1x="*" 2echo $x
Line 1 assigns a string literal containing one character (*) to variable x
. The output from the echo on line 2 will not be * (the value we assigned to x
) but bin spec, the two directories in our current working directory. Bash first expanded echo $x
to echo *
and it then matched *
to the content of the current working directory and expanded the command line to echo bin spec
. Globbing expansions like this are a source of often subtle and difficult to find bugs in Bash scripts, you can avoid them by simply enclosing all strings (or things you want to treat as a single string) in "". On the Bash command line try the following.
1x="*" 2echo "$x"
This time you see the * as intended. Enclosing the variable expansion in "" prevents Bash from globbing the expanded string.
What about ‘word splitting’2?
To understand the problem with word splitting we need to understand a bit more about how Bash processes commands. To keep things simple, and relevant to the current example, we will consider the echo
command.
The echo
command can be followed by a white space separated list of fields, it will then print to the screen a line of text constructed from these fields, each field separated by a single space3. The ‘word splitting’ problem arises when outputting variables containing strings with
multiple spaces. Try the following on the command line.
1x="magic disappearing spaces" 2echo $x
It does not matter how may spaces are between the words in the string assigned to x, so long as there are more than one to demonstrate the splitting issue.
echo $x
1magic disappearing spaces
All the space in the string is reduced to single space. This is because Bash first expands echo $x
to echo magic disappearing spaces
then outputs each ‘field’ (in this example the words ‘magic’, ‘disappearing’, and ‘spaces’) separated by a single space.
Now try using quotes.
1echo "${x}"
I’ve also included the {}
because, although unnecessary here, it is a good habit to develop.
echo "${x}"
1magic disappearing spaces
Notice that this time the space in the string is preserved, this is because Bash expands echo "${x}"
to echo "magic disappearing spaces"
and echo
sees the literal string as one field so
it is printed out literally.
This is ‘defensive programming’; programming to avoid problems that may occur if we take a more relaxed approach. Developing the habit of always using the longer form "${variable}"
you will avoid many painful debugging sessions.
Before moving on make sure bin/convert contains the correct code.
1greeting_message="Hello World!" 2echo "${greeting_message}"
And remember to run the tests to check your changes!