In both the Bourne shell and the C shell, lines of code are processed in multiple passes. The first pass is a parsing pass in which the basic structure of the line of code is extracted. In this pass, quotation marks serve as delimiters between individual pieces of information. For example, you can print a letter immediately after the contents of a variable without a space by closing (and reopening if necessary) the enclosing double quotes immediately after the variable name.
The second pass is an expansion pass. In this pass, any variable is expanded and any inline execution is performed. If a variable contains special characters, the resulting text is further expanded unless that variable is surrounded by double quotes. This may cause unexpected behavior if, for example, a variable contains a wildcard character.
Note: While the expansion of a variable or command inline will not cause a syntax error by itself, it can change the behavior of the eval command. See “Data Structures, Arrays, and Indirection” for more information.
Finally, the third pass is an execution pass. In this pass, the code is actually executed.
In some cases, you may need to change the way variable expansion takes place. You might want to use a nonstandard character to split a variable containing a list, change the way the shell handles special characters, or execute a command and substitute its output in the middle of another command. These techniques are described in the sections that follow.
Variable Expansion and Field Separators
Special Characters Explained
Quoting Special Characters
Inline Execution
In Bourne shell scripts, two operations are affected by the value of the IFS (internal field separators) shell variable: the read statement and variable expansion. The effect on the read statement is described separately in “Shell Variables and Printing.” In C shell scripts, only variable expansion is affected.
Whenever the shell expands a variable, the value of IFS comes into play. For example, the following script will print “a” and “b” on separate lines, then “c d” on a third line:
#!/bin/sh |
IFS=":" |
LIST="a:b:c d" |
for i in $LIST ; do |
echo $i |
done |
This occurs only because the value on the right side of the for statement contains a variable (LIST) that is expanded by the shell. When the shell expands the variable, it replaces the colon with a space and quotes any spaces in the original string. In effect, by the time the for statement sees the values, the right side of the for statement contains a b c\ d, just as in the example shown in “The for Statement.”
If you insert the exact contents of LIST on the right side of the variable, this script will instead print “a:b:c” on one line and “d” on the other. This is why it is very important to choose record separators correctly.
Cross-Platform Compatibility Note: This treatment of record separators is specific to BASH. Some Bourne shell variants use IFS when the shell splits a list even if no expansion is involved.
To avoid unexpected behavior, you should avoid setting nonstandard values for IFS except when you are expanding a shell variable that depends on this.
As an exception, it is safe to modify IFS during a read statement. Be sure to save the original value in another variable and restore it afterwards, however, to avoid unexpected behavior elsewhere in the script.
There are eight special characters in shell scripts: an asterisk (*), a question mark (?), curly braces ({ and }), square brackets ([ and ]) and single- and double-quote marks (‘ and “). You can use them as follows:
Dollar sign ($)—the first character in variable expansion, shell builtin math, and inline execution.
Asterisk (*)—a wildcard character that matches any number of characters. For example, ls *.jpg matches all files that end with the extension .jpg.
Question mark (?)—a wildcard character that matches a single character. For example, ls a?t.jpg would match both ant.jpg and art.jpg.
Curly braces—matches any of a series of options. For example, ls *.{jpg,gif} would match every file ending with either .jpg or .gif.
Square brackets—matches any of a series of characters. For example, ls a[rn]t.jpg would match art.jpg and ant.jpg, but would not match aft.jpg. The syntax of these character classes is similar to character classes in regular expressions, but there are a number of subtle differences. For more information, see the Open Group’s page on pattern matching notation at http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13.
Double-quote marks—disables argument splitting on word boundaries (spaces) and shell expansion of most special characters within the quote marks. As a special exception, variables are expanded within double-quote marks. The contents of variables, however, are not expanded.
Single-quote marks—disables argument splitting on word boundaries (spaces) and disables all shell expansion (including variables).
If your script accepts user input, these characters can produce unexpected results if you do not quote them properly. Consider the following example:
#!/bin/sh |
echo "Filename?" |
read NAME |
ls $NAME |
ls "$NAME" |
If a user types *.jpg at the prompt, the first command lists all files ending in .jpg because the variable is expanded first, and then the expression within it is expanded. The second command lists a single file (or prints an error if you don’t have a file named *.jpg).
Sometimes, when writing shell scripts, you may need to explicitly include quotation marks, dollar signs, or other special characters in your output. The way that you do this depends on the context.
If the character you wish to quote is not within quote marks, you probably should be. Otherwise, you have to deal with all of the shell special characters (described in “Special Characters Explained”) plus any new special characters that might be added in the future. This is particularly important if your script takes arbitrary user input and passes it as an argument.
However, if your script is not handling user input, you can quote a character by simply preceding it with a backslash (\). This tells the shell to treat it as a literal character instead of interpreting it normally. For example, the following code sample prints the word “Hello” enclosed in double-quotation marks.
echo \"Hello\" |
If the character you wish to quote is within double quotes, the same rules apply. The only difference is that with the exception of dollar signs and the double-quote marks themselves, you don’t need to quote special characters in this context. For example, to print the name of a variable followed by its value, you could write a statement like the following, which prints “The value of $VAR is 3” (with no quotes):
VAR=3 |
echo "The value of \$VAR is $VAR" |
Similarly, you can quote a backslash with another backslash if you need to print it. For example, the following statement prints “This \ is a backslash.“ (again, without quotes):
echo "This \\ is a backslash." |
If the character you wish to quote is within single quotes, shell expansion of special characters is disabled entirely. Thus, the only characters that are special are the single-quote marks themselves, because they terminate the single-quote context.
Because special character handling is disabled, a backslash does not quote anything between single-quote marks. Instead, a backslash is interpreted as literal text. Thus, to include a literal single quote within a double-quote context, you must terminate the single-quote context, then include the single quote (either by quoting it with a backslash or by surrounding it with double quotes), then start a new single-quote context.
For example, the following lines of code both print a popular phrase from an American children’s television show:
echo 'It'\''s a beautiful day in the neighborhood.' |
echo 'Won'"'"'t you be my neighbor?' |
C Shell Note: The C shell does not support using a backslash to quote a character within a double-quoted string. Thus, in the C shell, you print a backslash like this:
echo "This \ is a backslash." |
echo "This is "'$'"FOO" |
echo "This is "\$"FOO" |
The Bourne shell provides two operators for executing a command and placing its output in the middle of another command or string. These operators are the $() operator and the back-tick (`) operator (not to be confused with a normal single quote).
One common way to use this is for generating a list of filenames inline. For example, the grep command, when passed the -l flag, returns a list of files that match. This is often combined with the -r flag, which makes grep search recursively for files within any directories that it encounters in its file list. Thus,if you want to edit any files containing "myname" with vi, for example, you could do it like this:
vi $(grep -rl myname directory_of_files) |
You can, however, use this to execute any command. There is one small caveat you should be aware of, however. The back-tick operator cannot be nested. For example, the following command produces an error:
FOO=1; BAR=3 |
echo "Try this command: `echo $FOO + "`expr $BAR + 1`"`" |
This fails because the echo command ends at the second back-tick. Thus, the command executed is echo $FOO + ". If you need to nest inline execution, you can use the $() operator for the nested command. For example, the previous example can be written correctly as follows:
FOO=1; BAR=3 |
echo "Try this command: `echo $FOO + "$(expr $BAR + 1)"`" |
You should notice that double quotation marks can be safely nested within a command enclosed by either back-ticks or $().
Note: Evaluation of inline commands, much like expansion of variables, occurs after the statement itself is fully parsed. Thus, it is safe to use either the back-tick or $() operator even if the command may produce double-quote marks in its output. You do not need to quote the resulting content in any way.
C Shell Note: The C shell only partially supports this. It does not support the $() syntax. Its support for the back-tick syntax is somewhat limited; newlines in the result are always stripped and replaced with spaces. If you need to preserve newlines, you should store the results in a temporary file instead of in a shell variable.
Last updated: 2008-04-08