6.3 Bash Variables

On to our Bash example.

1cd ~/bash 
2shellspec
shellspec
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.

bin/convert
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.

bin/convert
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.

bin/convert
1greeting_message="Hello World!" 
2echo "${greeting_message}"

And remember to run the tests to check your changes!

1Actually it prints a newline with no message.

2Also called ‘field splitting’.

3This is not a complete description of echo , for a complete description refer to the man echo page.