11.2 Quoting and Literals
One of the most confusing elements of working with the shell and scripts is knowing when and why to use quotation marks (quotes) and other punctuation. Let’s say you want to print the string $100 and you do the following:
$ echo $100
00
Why did this print 00? Because $1 has a $ prefix, which the shell interprets as a shell variable (we’ll cover these soon). You think to yourself that maybe if you surround it with double quotes, the shell will leave the $1 alone:
$ echo "$100"
00
That still didn’t work. You ask a friend, who says that you need to use single quotes instead:
$ echo '$100'
$100
Why did this particular incantation work?
11.2.1 Literals
When you use quotes, you’re often trying to create a literal, a string that the shell should not analyze (or try to change) before passing it to the command line. In addition to the $ in the example that you just saw, this often comes up when you want to pass a * character to a command such as grep instead of having the shell expand it, and when you need to use a semicolon (;) in a command.
When writing scripts and working on the command line, remember what happens when the shell runs a command:
- Before running the command, the shell looks for variables, globs, and other substitutions and performs the substitutions if they appear.
- The shell passes the results of the substitutions to the command.
Problems involving literals can be subtle. Let’s say you’re looking for all entries in /etc/passwd that match the regular expression r.*t (that is, a line that contains an r followed by a t later in the line, which would enable you to search for usernames such as root and ruth and robot). You can run this command:
$ grep r.*t /etc/passwd
It works most of the time, but sometimes it mysteriously fails. Why? The answer is probably in your current directory. If that directory contains files with names such as r.input and r.output, then the shell expands r.*t to r.input r.output and creates this command:
$ grep r.input r.output /etc/passwd
The key to avoiding problems like this is to first recognize the characters that can get you in trouble and then apply the correct kind of quotes to protect those characters.
11.2.2 Single Quotes
The easiest way to create a literal and make the shell leave a string alone is to enclose the entire string in single quotes ('), as in this example with grep and the * character:
$ grep 'r.*t' /etc/passwd
As far as the shell is concerned, all characters between two single quotes, including spaces, make up a single parameter. Therefore, the following command does not work, because it asks the grep command to search for the string r.*t /etc/passwd in the standard input (because there’s only one parameter to grep):
$ grep 'r.*t /etc/passwd'
When you need to use a literal, you should always turn to single quotes first, because you’re guaranteed that the shell won’t try any substitutions. As a result, it’s a generally clean syntax. However, sometimes you need a little more flexibility, so you can turn to double quotes.
11.2.3 Double Quotes
Double quotes (") work just like single quotes, except that the shell expands any variables that appear within double quotes. You can see the difference by running the following command and then replacing the double quotes with single quotes and running it again.
$ echo "There is no * in my path: $PATH"
When you run the command, notice that the shell substitutes for $PATH but does not substitute for the *.
If you’re using double quotes when working with large amounts of text, consider using a here document, as described in Section 11.9.
11.2.4 Literal Single Quotes
Using literals with the Bourne shell can be tricky when you’re passing a literal single quote to a command. One way to do this is to place a backslash before the single quote character:
$ echo I don\'t like contractions inside shell scripts.
The backslash and quote must appear outside any pair of single quotes. A string such as 'don\'t results in a syntax error. Oddly enough, you can enclose the single quote inside double quotes, as shown in the following example (the output is identical to that of the preceding command):
$ echo "I don't like contractions inside shell scripts."
If you’re in a bind and you need a general rule to quote an entire string with no substitutions, follow this procedure:
- Change all instances of
'(single quote) to'\''(single quote, backslash, single quote, single quote). - Enclose the entire string in single quotes.
Therefore, you can quote an awkward string such as this isn't a forward slash: \ as follows:
$ echo 'this isn'\''t a forward slash: \'
It's worth repeating that when you quote a string, the shell treats everything inside the quotes as a single parameter. Therefore, a b c counts as three parameters, but a "b c" is only two.