Differences between revisions 6 and 7
Revision 6 as of 2011-04-21 19:44:39
Size: 5404
Editor: GreyCat
Comment: "..."'...'
Revision 7 as of 2011-10-13 20:16:08
Size: 6504
Editor: GreyCat
Comment: clarify language and examples. explain "$@" vs "$*". additional examples. remove dubious/unnecessary stuff.
Deletions are marked like this. Additions are marked like this.
Line 3: Line 3:
= Standard quoting = == Standard quoting ==
Line 19: Line 19:
The second purpose of quoting is to prevent WordSplitting and [[glob|globbing]]. The value of a double-quoted substitution does ''not'' undergo any further processing. (An unquoted substitution ''does''.) The second purpose of quoting is to prevent WordSplitting and [[glob|globbing]]. The result of a double-quoted substitution does ''not'' undergo any further processing. (The result of an unquoted substitution ''does''.)
Line 25: Line 25:
In this example, the double quotes protect each parameter (variable) from undergoing word splitting or globbing should it happen to contain whitespace or wildcard characters (`*` or `?` or `[...]`). Without the quotes, a `filename` like `hot stuff.mp3` would be split into two words, and each word would be passed to the `cp` command as a separate argument. Or, a `filename` that contains `*` with whitespace around it would produce one word for every file in the current directory. That is not what we want. In this example, the double quotes protect the value of each parameter (variable) from undergoing word splitting or globbing should it happen to contain whitespace or wildcard characters (`*` or `?` or `[...]`). Without the quotes, a `filename` like `hot stuff.mp3` would be split into two words, and each word would be passed to the `cp` command as a separate argument. Or, a `filename` that contains `*` with whitespace around it would produce one word for every file in the current directory. That is not what we want.
Line 29: Line 29:
When in doubt, quote your parameter expansions. It's rare that you would need one to be unquoted, and if that's the case, you'll know it. When in doubt, always double-quote your parameter expansions.
Line 39: Line 39:
Double-quoting `$@` or `${array[@]}` has a special meaning. `"$@"` expands to a list of words, with each positional parameter's value being one word. Likewise, `"${array[@]}"` expands to a list words, one per array element. When dealing with the positional parameters or with the contents of an array as a list of words, ''always'' use the double-quoted syntax.

Double-quoting `$*` or `${array[*]}` results in ''one word'' which is the concatenation of all the positional parameters (or array elements) with the first character of [[IFS]] between them. This is similar to the `join` function in some other languages.
Line 43: Line 47:

# Never use for file in $* or for file in $@
Line 50: Line 56:
for index in ${!array[*]}
# We omit quotes here because the indices are a list of numbers,
# which must be word-split in order to be processed. Numbers will
# never undergo glob expansion either, so we can skip "set -f".

# We could also write it this way, and it would still work:
# bash 3.0 and higher
Line 58: Line 59:
# The second version has the advantage of continuing to work if
# the array is an associative array rather than a normal array.
# This works with both regular and associative arrays. (Associative
# arrays require bash 4.0 or higher.)
Line 63: Line 64:
# bash or ksh93
Line 73: Line 75:
Line 75: Line 78:

echo "The matching line is: $(grep foo "$filename")"
                                       ^---------^ inner layer (quotes)
                            ^^--------------------^ middle layer (command sub)
     ^---------------------------------------------^ outer layer (quotes)

# Without the inner quotes, the value of $filename would be word-split and globbed
# before being handed to grep.
}}}

{{{
# bash
ip=192.168.1.30
netmask=255.255.254.0
IFS=. read -ra ip_octets <<< "$ip"
IFS=. read -ra netmask_octets <<< "$netmask"
for i in 0 1 2 3; do
  ((ip_octets[i] &= netmask_octets[i]))
done
IFS=.; network="${ip_octets[*]}"; unset IFS
Line 79: Line 102:
= Bash extensions = == Bash extensions ==
Line 87: Line 110:
}}}

{{{
echo $'It\'s also easier to escape apostrophes this way.'

Quoting in shell programming is extremely important. There are multiple types of quotes, and you must know how and when to use each type.

Standard quoting

A shell command is parsed by the shell into words, using whitespace (regardless of the IFS variable) and other shell metacharacters. The first function of quoting is to permit words to contain these metacharacters.

echo '&'

Without quotes, the & would put the echo command into the background. With quotes, the & is simply made into a word, and passed as an argument to the echo command instead.

The quotes are not actually passed along to the command. They are removed by the shell. In the example above, the echo command sees only the &, not the quotes.

Single quotes (apostrophes) prevent all interpretation of the characters between them.

Double quotes permit parameter expansions, arithmetic expansions and command substitutions to occur inside them. They do not allow filename expansions, brace expansions, process substitutions, tilde expansion, etc. In short, any substitution that starts with $ is allowed, and also ` for legacy compatibility.

The second purpose of quoting is to prevent WordSplitting and globbing. The result of a double-quoted substitution does not undergo any further processing. (The result of an unquoted substitution does.)

cp -- "$filename" "$destination"

In this example, the double quotes protect the value of each parameter (variable) from undergoing word splitting or globbing should it happen to contain whitespace or wildcard characters (* or ? or [...]). Without the quotes, a filename like hot stuff.mp3 would be split into two words, and each word would be passed to the cp command as a separate argument. Or, a filename that contains * with whitespace around it would produce one word for every file in the current directory. That is not what we want.

With the quotes, every character in the value of the filename parameter is treated literally, and the whole value becomes the second argument to the cp command.

When in doubt, always double-quote your parameter expansions.

You may concatenate the various types of quoting if you need to. For example, if you have one section of a string that has lots of special characters that you'd like to single-quote, and another section with a parameter expansion in it which must be double-quoted, you may mix them:

echo '!%$*&'"$foo"

Any number of quoted substrings, of any style, may be concatenated in this manner. The result (after appropriate expansions in the double-quoted sections) is a single word.

Double-quoting $@ or ${array[@]} has a special meaning. "$@" expands to a list of words, with each positional parameter's value being one word. Likewise, "${array[@]}" expands to a list words, one per array element. When dealing with the positional parameters or with the contents of an array as a list of words, always use the double-quoted syntax.

Double-quoting $* or ${array[*]} results in one word which is the concatenation of all the positional parameters (or array elements) with the first character of IFS between them. This is similar to the join function in some other languages.

Assorted examples, to show how things should be done. Some of these examples use bash/ksh syntax that won't work in strict POSIX shells.

for file in "$@"

# Never use  for file in $*  or  for file in $@

for element in "${array[@]}"

# bash 3.0 and higher
for index in "${!array[@]}"

# This works with both regular and associative arrays.  (Associative
# arrays require bash 4.0 or higher.)

# bash or ksh93
find_opts=('(' -iname '*.jpg' -o -iname '*.gif' -o -iname '*.png' ')')
find . "${find_opts[@]}" -print

echo 'Don'\''t walk!'

echo "The matching line is: $(grep foo "$filename")"

# Note that the quotes inside the $() command substitution are nested.
# This looks wrong to a C programmer, but it is correct in shells.

echo "The matching line is: $(grep foo "$filename")"
                                       ^---------^    inner layer (quotes)
                            ^^--------------------^   middle layer (command sub)
     ^---------------------------------------------^  outer layer (quotes)

# Without the inner quotes, the value of $filename would be word-split and globbed
# before being handed to grep.

# bash
ip=192.168.1.30
netmask=255.255.254.0
IFS=. read -ra ip_octets <<< "$ip"
IFS=. read -ra netmask_octets <<< "$netmask"
for i in 0 1 2 3; do
  ((ip_octets[i] &= netmask_octets[i]))
done
IFS=.; network="${ip_octets[*]}"; unset IFS

The third type of quote is the backtick (`) or back quote. It's a deprecated markup for command substitutions, used in Bourne shells before the introduction of the $(...) syntax. For a discussion of the difference between `...` and $(...) please see BashFAQ/082.

Bash extensions

Bash introduces two additional forms of quoting. The first is $'...' which acts like single quotes except that backslash-escaped combinations are expanded as specified by the ANSI C standard. This allows a convenient way to embed nonprintable characters into strings, or to pass them as arguments.

IFS=$' \t\n'
# sets the IFS variable to the three-byte string containing
# a space, a tab, and a newline

echo $'It\'s also easier to escape apostrophes this way.'

The second form is $"..." and is used for localization support.

In addition to these, bash uses quotes to suppress the "specialness" of the right-hand-side of an = or =~ operator inside a [[ keyword. That sounds complicated, but it's simpler when shown as an example:

if [[ $path = foo* ]]; then
# unquoted foo* acts as a glob

if [[ $path = "foo*" ]]; then
# quoted "foo*" is a literal string

if [[ $path =~ $some_re ]]; then
# the contents of $some_re are treated as an ERE

if [[ $path =~ "$some_re" ]]; then
# the contents of $some_re are treated as a literal string
# despite the =~ operator

See glob and RegularExpression for explanations of those terms.


CategoryShell

Quotes (last edited 2024-03-07 22:57:49 by emanuele6)