Bash from the ground up

Values in variables, and quoting

Naming a value, the strict rule about spaces around the equals sign, and the everyday difference between "$name" and a pair of single quotes.

Part 2 of 12

Beginner16 min readBack to Courses

Giving a value a name

A variable is a name for a value. You set one by writing the name, an equals sign and the value, with no spaces on either side of the equals sign — name=Ada works, name = Ada does not, because the shell would read it as a command called name. A space changes the first word from an assignment into a command name.

To read the value back, put a dollar in front of the name: "Hello, $name". When you assign, there is no dollar. When you use, there is. That one difference trips up almost everyone in their first week. A dollar on the left is read before the assignment, so it asks for the old value.

Quoting decides what the shell touches

Double quotes still expand a variable, so "$name" becomes the value. Single quotes take everything literally, so a dollar inside them stays a dollar — Hello, $name as written. That makes single quotes the right choice for text that must arrive unchanged. The choice is about what the shell is allowed to do before the command sees the text.

Unquoted, the shell splits a value on its spaces before passing it along. A file name stored as my notes.txt becomes two arguments if you leave the quotes off, and the command then sees two names instead of one. The habit worth forming now: put double quotes around every variable you use, unless you have a reason not to. A quoted empty value is one empty argument; an unquoted one disappears entirely.

Reading a value, several ways

fruit=apple
price=3

echo "$fruit"          # apple
echo '$fruit'          # $fruit
echo "cost: $price"    # cost: 3

# Braces make the end of the name clear.
echo "${fruit}s"       # apples
echo "$fruits"         # nothing: there is no such name

What a variable actually holds

Every shell variable holds text. count=3 stores the character 3, not a number, and arithmetic works later only because $(( )) reads that text as a number. There are no declarations and no types: assigning again replaces whatever was there, even when the new value has a different shape. That is why a value from a file or a command needs no conversion before it is printed.

A variable belongs to the shell that created it. A command the script runs is a child process and does not see the name unless it was marked with export, which is why a setting can look present on one line and missing in the next program. Reading a name that was never set gives an empty string and no warning at all. env shows what a child will receive; the parent’s other names stay behind.

Braces, defaults and lengths

Braces tell the shell exactly where a name ends. ${fruit}s is the value of fruit followed by an s, while $fruits asks for a name called fruits. The braces cost two characters and remove a whole class of silent mistakes. The rule is not limited to letters after a name: any character that could be part of a name needs the braces to separate it.

The same braces carry small operations: ${#name} is the number of characters, ${name:-guest} supplies a fallback when the name is empty or unset, and ${name%%.*} strips a suffix. They are part of the expansion rather than separate commands, so they work anywhere a value is allowed. The fallback is used for an empty value as well as an unset one, which is often what you want and sometimes a surprise.

Reading an expansion from the inside out

An expansion is read from the inside out. ${name:-guest} first asks whether name has a value; if it does, that value is the answer, and if it does not, the text after :- is. The braces are the boundary that tells the shell which question you are asking.

The same reading explains why quotes matter around the result. "${name:-guest}" is one value even when it contains spaces, while the unquoted form can split into several. Build the habit of writing the braces and the quotes together; the bugs they prevent are the quiet kind that produce a plausible wrong answer.

Quotes change what the program passes on

Run it and compare the two bracket lines: one value, two arguments. Then set `name` to your own name, print it with double quotes and with single quotes, and add a line that builds a new sentence out of two variables.

Output
Click "Run" to execute your code

A name that was never set expands to nothing at all, and the shell says no word about it. If you run a script and something is quietly missing from the output, check for a spelling mistake in a name before you check anything else.

Watch three things: a space on either side of = turns the assignment into a command, single quotes stop expansion so a $ inside them stays a dollar, and an unquoted value splits on spaces before the next command sees it. The first is an error, the second is usually a surprise, and the third quietly turns one argument into several.

Try these next

  • Set city and print a sentence with it inside double quotes, then inside single quotes.
  • Print the length of a name with ${#name}, and a fallback with ${name:-unknown} after unset name.
  • Join two variables with braces so that no missing name appears: "${first}${last}".

Fallbacks, lengths and a joined name

Run it, then give `middle` a value and watch the fallback disappear. Add a variable of your own and print its length.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

Which line sets the variable count to 3?