Sometimes I see shell scripts use all of these different ways of quoting some text: "..."
, '...'
, $'...'
, and $"..."
. Why are there so many different kinds of quote being used?
Do they behave differently or affect what I can do inside them?
bashquotingshellshell-scriptzsh
Sometimes I see shell scripts use all of these different ways of quoting some text: "..."
, '...'
, $'...'
, and $"..."
. Why are there so many different kinds of quote being used?
Do they behave differently or affect what I can do inside them?
The uninterpreted shell arguments are $1
, $2
, etc. You need to put their expansion in double quotes in most contexts, to avoid the value of the parameter being expanded further. "$@"
gives you the list of all parameters.
For example, if you want to pass an argument of the shell script to your function, call it like this:
first_argument_as_filename_in_unix_syntax=$(posix "$1")
The double quotes are necessary. If you write posix $1
, then what you're passing is not the value of the first parameter but the result of performing word splitting and globbing on the value of the first parameter. You will need to use proper quoting when calling the script, too. For example, if you write this in bash:
myscript c:\path with spaces\somefile
then the actual, uninterpreted arguments to myscript
will be c:path
, with
and spacessomefile
. So don't do this.
Your posix
function is wrong, again because it lacks double quotes around $1
. Always put double quotes around variable and command substitutions: "$foo"
, "$(foo)"
. It's easier to remember this rule than the exceptions where you don't actually need the quotes.
echo
does its own processing in some cases, and calling external processes is slow (especially on Windows). You can do the whole processing inside bash.
posix () {
path="${1//\\//}"
case "$path" in
?:*) drive="${p:0:1}"; drive="${drive,}"; p="/$drive/${p:2}";;
esac
printf %s "$p"
}
The zsh feature that jw013 alluded to doesn't do what you seem to think it does. You can put noglob
in front of a command, and zsh does not perform globbing (i.e. filename generation, i.e. expansion of wildcards) on the arguments. For example, in zsh, if you write noglob locate *foo*bar*
, then locate
is called with the argument *foo*bar*
. You'd typically hide the noglob
builtin behind an alias. This feature is irrelevant for what you're trying to do.
First, separate zsh from the rest. It's not a matter of old vs modern shells: zsh behaves differently. The zsh designers decided to make it incompatible with traditional shells (Bourne, ksh, bash), but easier to use.
Second, it is far easier to use double quotes all the time than to remember when they are needed. They are needed most of the time, so you'll need to learn when they aren't needed, not when they are needed.
In a nutshell, double quotes are necessary wherever a list of words or a pattern is expected. They are optional in contexts where a raw string is expected by the parser.
Note that without double quotes, two things happen.
${foo}
, or the output of the command for a command substitution like $(foo)
) is split into words wherever it contains whitespace.IFS
variable (separator character). If a sequence of separator characters contains whitespace (space, tab or newline), the whitespace is counts as a single character; leading, trailing or repeated non-whitespace separators lead to empty fields. For example, with IFS=" :"
, :one::two : three: :four
produces empty fields before one
, between one
and two
, and (a single one) between three
and four
.\[*?
. If that pattern matches one or more file names, the pattern is replaced by the list of matching file names.An unquoted variable expansion $foo
is colloquially known as the “split+glob operator”, in contrast with "$foo"
which just takes the value of the variable foo
. The same goes for command substitution: "$(foo)"
is a command substitution, $(foo)
is a command substitution followed by split+glob.
Here are all the cases I can think of in a Bourne-style shell where you can write a variable or command substitution without double quotes, and the value is interpreted literally.
On the right-hand side of an assignment.
var=$stuff
a_single_star=*
Note that you do need the double quotes after export
, because it's an ordinary builtin, not a keyword. This is only true in some shells such as dash, zsh (in sh emulation), yash or posh; bash and ksh both treat export
specially.
export VAR="$stuff"
In a case
statement.
case $var in …
Note that you do need double quotes in a case pattern. Word splitting doesn't happen in a case pattern, but an unquoted variable is interpreted as a pattern whereas a quoted variable is interpreted as a literal string.
a_star='a*'
case $var in
"$a_star") echo "'$var' is the two characters a, *";;
$a_star) echo "'$var' begins with a";;
esac
Within double brackets. Double brackets are shell special syntax.
[[ -e $filename ]]
Except that you do need double quotes where a pattern or regular expression is expected: on the right-hand side of =
or ==
or !=
or =~
.
a_star='a*'
if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
fi
You do need double quotes as usual within single brackets [ … ]
because they are ordinary shell syntax (it's a command that happens to be called [
). See Single or double brackets
In a redirection in non-interactive POSIX shells (not bash
, nor ksh88
).
echo "hello world" >$filename
Some shells, when interactive, do treat the value of the variable as a wildcard pattern. POSIX prohibits that behaviour in non-interactive shells, but a few shells including bash (except in POSIX mode) and ksh88 (including when found as the (supposedly) POSIX sh
of some commercial Unices like Solaris) still do it there (bash
does also attempt splitting and the redirection fails unless that split+globbing results in exactly one word), which is why it's better to quote targets of redirections in a sh
script in case you want to convert it to a bash
script some day, or run it on a system where sh
is non-compliant on that point, or it may be sourced from interactive shells.
Inside an arithmetic expression. In fact, you need to leave the quotes out in order for a variable to be parsed as an arithmetic expression.
expr=2*2
echo "$(($expr))"
However, you do need the quotes around the arithmetic expansion as they are subject to word splitting in most shells as POSIX requires (!?).
In an associative array subscript.
typeset -A a
i='foo bar*qux'
a[foo\ bar\*qux]=hello
echo "${a[$i]}"
An unquoted variable and command substitution can be useful in some rare circumstances:
$IFS
was not modified and you want to split it at whitespace characters.set -f
, set IFS
to the separator character (or leave it alone to split at whitespace), then do the expansion.In zsh, you can omit the double quotes most of the times, with a few exceptions.
$var
never expands to multiple words, however it expands to the empty list (as opposed to a list containing a single, empty word) if the value of var
is the empty string. Contrast:
var=
print -l $var foo # prints just foo
print -l "$var" foo # prints an empty line, then foo
Similarly, "${array[@]}"
expands to all the elements of the array, while $array
only expands to the non-empty elements.
The @
parameter expansion flag sometimes requires double quotes around the whole substitution: "${(@)foo}"
.
Command substitution undergoes field splitting if unquoted: echo $(echo 'a'; echo '*')
prints a *
(with a single space) whereas echo "$(echo 'a'; echo '*')"
prints the unmodified two-line string. Use "$(somecommand)"
to get the output of the command in a single word, sans final newlines. Use "${$(somecommand; echo _)%?}"
to get the exact output of the command including final newlines. Use "${(@f)$(somecommand)}"
to get an array of lines from the command's output.
Best Answer
All of these mean something different, and you can write different things inside them (or the same things, with different meaning). Different kinds of quote interpret different escape sequences inside them (
\something
), or do or don't allow variable interpolations ($something
) and other sorts of expansion inside them.In short:
'...'
is entirely literal."..."
allows both variables and embedded quote characters.$'...'
performs character escapes like\n
, but doesn't expand variables.$"..."
is for human-language translations in Bash and ksh.'Single quotes'
Whatever you write between single quotes is treated literally and not processed at all. Backslashes and dollar signs have no special meaning there. This means you can't backslash-escape a character (including other single quotes!), interpolate a variable, or use any other shell feature.
All of these examples result in literally what's written between the quotes:
'hello world'
'/pkg/bin:$PATH'
'hello\nworld'
'`echo abc`'
'I\'dn\'t've'
The last one is complicated - there are two single-quoted strings run together with some unquoted text. The first one contains
I\
. The unquoted textdn\'t
contains a single quote that's escaped at the shell level, so it doesn't start a quoted string and is included as a literal character (so,dn't
). The final quoted string is justve
. All of those get run together into a single word in the usual way the shell works.A somewhat-common idiom for combining literal text and variables is to run them together like that:
will result in
as a single word (better to double-quote
$PATH
as well just in case - spaces or globbing characters in the variable value may be processed otherwise - but for the sake of a readable running example I haven't)."Double quotes"
Inside double quotes, two sorts of expansion are processed, and you can use a backslash to escape characters to prevent expansions or escapes from being processed.
There are two categories of expansion that happen inside double quotes:
$
(parameter expansion$abc
and${abc}
, command substitution$(...)
, and arithmetic expansion$((...))
);`abc`
;Inside the quotes, a backslash can inhibit those expansions by putting it before the
$
or`
. It can also escape a closing double quote, so\"
includes just"
in your string, or another backslash. Any other backslash is preserved literally - there are no escapes to produce other characters, and it isn't removed.Some of these examples act differently to before, and some don't:
"hello world"
"/pkg/bin:$PATH"
"hello\nworld"
"hello\\nworld"
"`echo abc`"
"I\'dn\'t've"
"I'dn't've"
"I\"dn\"t've"
$'ANSI-C quoting'
This kind of quoting allows C-style backslash escapes to be processed, but not embedded variables or substitutions. It's the only kind of quoting that supports character escapes.
This is an extension from ksh, now supported in Bash, zsh, and some other shells as well. It is not yet part of the POSIX standard and so maximally-portable scripts can't use it, but a Bash or ksh script is free to.
All of these escapes can be used with their C meanings:
\a
,\b
,\f
,\n
,\r
,\t
,\v
, and the literal escapes\\
,\'
,\"
, and\?
. They also support the extensions\e
(escape character) and in Bash and ksh\cx
(what would be entered by Ctrl-x, e.g.\cM
is carriage return). Shells have a range of minor extensions of their own.It also allows four kinds of generic character escapes:
\nnn
, a single byte with octal value nnn\xHH
, a single byte with hexadecimal value HH\uHHHH
, the Unicode codepoint whose hexadecimal index is HHHH\UHHHHHHHH
, the Unicode codepoint whose hexadecimal index is HHHHHHHHAll of those digits are optional after the first one.
$
and`
have no meaning and are preserved literally, so you can't include a variable there.$'hello world'
$'/pkg/bin:$PATH'
$'hello\nworld'
world
$'`echo abc`'
$'I\'dn\'t\'ve'
$'\U1f574\u263A'
Most of these escapes you can simulate using the
printf
command, though POSIX only requires\\
,\a
,\b
,\f
,\n
,\r
,\t
,\v
, and\nnn
to work there. You can use command substitution to embed aprintf
inside double quotes if needed:"Path:$(printf '\t')$PATH"
.$"Locale translation"
This is a ksh- and Bash-specific extension for localising natural-language textual strings, and looks up the part inside the quotes in a message catalog. It performs all the double quote expansions first. If the string isn't found in the translation database, it's used as its own translation. The built-in assumption is that the strings are in English.
You probably don't want to use this one, but if you see it you can generally treat it as regular double quotes.
One point of note is that there is no kind of quoting that allows both embedded parameter expansion and embedded character escapes. In most cases where you would want that, you'd be better off (safer) using
printf
:This clearly separates which parts are subject to character escaping and which are data values.
Another is that all of these styles of quoting create a single "word" in the shell, unless
$@
or an array expansion${x[@]}
is used inside double quotes. Both single-quote forms are always one word and never expanded any further.