179

The old advice used to be to double-quote any expression involving a $VARIABLE, at least if one wanted it to be interpreted by the shell as one single item, otherwise, any spaces in the content of $VARIABLE would throw off the shell.

I understand, however, that in more recent versions of shells, double-quoting is no longer always needed (at least for the purpose described above). For instance, in bash:

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

In zsh, on the other hand, the same three commands succeed. Therefore, based on this experiment, it seems that, in bash, one can omit the double quotes inside [[ ... ]], but not inside [ ... ] nor in command-line arguments, whereas, in zsh, the double quotes may be omitted in all these cases.

But inferring general rules from anecdotal examples like the above is a chancy proposition. It would be nice to see a summary of when double-quoting is necessary. I'm primarily interested in zsh, bash, and /bin/sh.

kjo
  • 15,339
  • 25
  • 73
  • 114

1 Answers1

210

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 single raw string is expected by the parser.

What happens without quotes

Note that without double quotes, two things happen.

  1. First, the result of the expansion (the value of the variable for a parameter substitution like $foo or ${foo}, or the output of the command for a command substitution like $(foo)) is split into words wherever it contains whitespace.
    More precisely, the result of the expansion is split at each character that appears in the value of the IFS variable (separator character). If a sequence of separator characters contains whitespace (space, tab, or newline), the whitespace counts as a single character; leading, trailing, or repeated non-whitespace separators lead to empty fields. For example, with IFS=" :" (space and colon), :one::two : three: :four  produces empty fields before one, between one and two, and (a single one) between three and four.
  2. Each field that results from splitting is interpreted as a glob (a wildcard pattern) if it contains one of the characters [*?. 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.

Where you can omit the double quotes

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 a scalar (not array) variable assignment.

     var=$stuff
     a_single_star=*
    

    Note that you do need the double quotes after export or readonly, because in a few shells, they are still ordinary builtins, not a keyword. This is only true in some shells such as some older versions of dash, older versions of zsh (in sh emulation), yash, or posh; in bash, ksh, newer versions of dash and zsh export / readonly and co are treated specially as dual builtin / keyword (under some conditions) as POSIX now more clearly requires.

     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 glob-style 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 =~ (though for the latter, behaviour varies between shells).

     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 Why does parameter expansion with spaces without quotes work inside double brackets "[[" but not inside single 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 in several shells.

     expr=2*2
     echo "$(($expr))"
    

    However, you do need the quotes around the arithmetic expansion as it is 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:

  • When the variable value or command output consists of a list of glob patterns and you want to expand these patterns to the list of matching files.
  • When you know that the value doesn't contain any wildcard character, that $IFS was not modified and you want to split it at whitespace (well, only space, tab and newline) characters.
  • When you want to split a value at a certain character: disable globbing with set -o noglob / set -f, set IFS to the separator character (or leave it alone to split at whitespace), then do the expansion.

Zsh

In zsh, you can omit the double quotes most of the time, with a few exceptions.

  • $var never expands to multiple words (assuming var isn't an array), but 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.

  • Like in ksh and bash, inside [[ … ]], a variable in the right-hand side of a ==, != or =~ operator needs to be double-quoted if it contains a string, and not quoted if it contains a pattern/regex: p='a*'; [[ abc == $p ]] is true but p='a*'; [[ abc == "$p" ]] is false.

  • 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 (removing trailing empty lines if any though).

  • In fact, you need to leave the quotes out in order for a variable to be parsed as an arithmetic expression. Why am I able to make your example work with quotes: echo "$(("$expr"))" – Cyker Nov 29 '16 at 04:05
  • This is what man bash says: The expression is treated as if it were within double quotes, but a double quote inside the parentheses is not treated specially. – Cyker Nov 29 '16 at 04:20
  • 8
    Also, for anyone who is interested, the formal names of split+glob are word splitting and pathname expansion. – Cyker Nov 29 '16 at 04:21
  • 4
    FYI -- over on StackOverflow, I've had someone pull the "optional when a raw string is expected" language in this answer out to defend not quoting an argument to echo. It might be worth trying to make the language even more explicit ("when a raw string is expected by the parser", perhaps?) – Charles Duffy Mar 10 '17 at 04:01
  • 3
    @CharlesDuffy Ugh, I hadn't thought of this misreading. I've changed “where” to “when” and reinforced the sentence as you suggested. – Gilles 'SO- stop being evil' Mar 10 '17 at 11:04
  • Great answer. There's one other place you don't need quotes, which is when using + in variable substitution, e.g. ${VARNAME+replacement}, although you do need to quote the replacement if it has whitespace or contains any variables, e.g. ${VARNAME:+--opt="$VARNAME"}. – Alex Dupuy Nov 29 '17 at 23:04
  • "${$(somecommand; echo _)%?}" technically outputs the exact output of the command, but it also adds an additional newline to the end. "${"${$(df|dfalign; echo _)%?}"%$'\n'}" appears to correct this.2) It isn't stated what the expected output of "${(@f)$(somecommand)}", to get an array, should be. The result is, all trailing newlines are remove. If you require an array with trailing newlines, "${(@f)"${$(somecommand; echo _)%?}"}" comes close, but you will have to deal with the additional newline.
  • – Friartek Dec 14 '17 at 16:12
  • Sorry, ignore my previous comment, it was added before complete and edited within time limit. 1) "${$(somecommand; echo _)%?}" technically outputs the exact output of the command, but it also adds an additional newline to the end. "${"${$(somecommand; echo _)%?}"%$'\n'}" appears to correct this. 2) It isn't stated what the expected output of "${(@f)$(somecommand)}", to get an array, should be. The result is, all trailing newlines are remove. If you require an array with trailing newlines, "${(@f)"${$(somecommand; echo _)%?}"%$'\n'}" should give you the desired result. – Friartek Dec 14 '17 at 16:33
  • @Friartek No: "${$(somecommand; echo _)%?}" is the exact output of the command, including a final newline if there is one (and there usually is). If you want the output of the command without trailing newlines, it's just "$(somecommand; echo _)". – Gilles 'SO- stop being evil' Dec 14 '17 at 18:22
  • @Gilles Hi. Sorry, not what I'm seeing. This is how I understand what is going on. $() will strip all trailing blank lines from a command within the parentheses. By adding ;echo _ to the end of the output of "somecommand", this tacks on an additional line with an underscore so any trailing blank lines from "somecommand" will be protected from $(). %? then deletes the underscore, leaving an empty line which wasn't there before. This is especially noticeable when there are no trailing blank lines from "somecommand". Am I missing or misinterpreting something here? – Friartek Dec 15 '17 at 01:40
  • @Friartek You keep mentioning an “empty line which wasn't there before”, but I don't understand why you think there is such a thing. Adding ;echo _ adds an underscore and a line break. The command substitution removes this line break. ${…%?} removes the underscore. You're left with exactly the output of the command, including its trailing line breaks if any. – Gilles 'SO- stop being evil' Dec 15 '17 at 10:54
  • @Gilles Being ill and on antibiotics is no excuse for dumb mistakes on my part. Let myself get bit by the evil quote gods while working with arrays, then applied those results back to your original code. ${…%?} does work as you said and is a simple way around $(...) stripping trailing "empty lines". Still having a problem with the term "line break", took it to mean some quite different. My use case now is array=(${(@f)"${$(somecommand; echo _)%?}"}). I apologize to you and all here for the noise. I will delete some of my previous comments so others wont make my mistake. – Friartek Dec 15 '17 at 15:12
  • "Except that you do need double quotes where a pattern or regular expression is expected: on the right-hand side of = or == or != or =~." Perhaps I'm misunderstanding this, but AFAIK, a RHS regular expression (i.e. after =~) should always be unquoted (since Bash 3.2 at least). – Jani Uusitalo Sep 08 '20 at 12:38
  • @JaniUusitalo That's the point: if you leave the variable unquoted, it's interpreted as a regular expression. If you quote it, it's interpreted as a substring. So for example [[ $x =~ ^$a ]] checks whether the value of x starts with a string that matches the regular expression contained in a, whereas [[ $x =~ ^"$a" ]] checks whether the value of x starts with the string which is the value of a. – Gilles 'SO- stop being evil' Sep 08 '20 at 13:10
  • @Gilles'SO-stopbeingevil' Ok, it was just my misunderstanding then; I interpreted the quote as meaning "you should quote RHS regular expressions". – Jani Uusitalo Sep 08 '20 at 17:55
  • I set a variable like: o='OneDrive - MyCompany'. Then I use it: cd "$o"/Docu. Then I press TAB to do word completion on the folder name. The result is cd $o/Documents. I hit enter and it fails because it stole my double quotes. I want to use "$o" as a very fast shortcut for that directory name. I don't want to type "$o/Docu and then press TAB and then ", which works. When I start typing I don't want to think about whether I might press TAB to expand something later in the path. I just want to follow the rule to double quote variables. That doesn't work here. Any suggestions? – All The Rage Nov 09 '20 at 17:40
  • I really want to be able to put backslashes before the spaces when defining the variable and have them be respected after expanding it. Is there any way I can do that? It would solve this problem so nicely. – All The Rage Nov 09 '20 at 17:42
  • @user7392 I don't know. You should ask a new question. – Gilles 'SO- stop being evil' Nov 09 '20 at 17:44
  • 3
    Super answer. Just reinforcing what a mess it as and to... Quote unless you have a specific reason not to – RichieHH Feb 19 '21 at 07:32
  • (1) I just stumbled across this (again) and I was struck / puzzled by your statement “On the right-hand side of a scalar (not array) variable assignment.” (emphasis added)  Perhaps you should clarify that colors[1]=$ruby_color / colors[2]=$pumpkin_color / colors[3]=$schoolbus_color / colors[4]=$grass_color, etc., are fine, and maybe give a suitable caveated example of what doesn’t work. P.S.  I’m on a personal crusade to stamp out the phrases “left-hand” and “right-hand” except when talking about gloves or silverware etiquette.  Why not just say “right side”? … (Cont’d) – G-Man Says 'Reinstate Monica' Aug 27 '22 at 08:54
  • (Cont’d) …  (2) You might want to link to How can I use a variable as a case condition? and point out that, while a *, ? or […] in an unquoted variable in a case pattern is interpreted as a glob-style pattern, a | (being shell syntax rather than glob syntax) does *not* delimit multiple glob-style patterns.  (3) You might want to add example(s) for =~ to your discussion of [[]].  c.t and c.*t are pedagogically useful, with test strings like “cat”, “city”, “coat” and “cot”.  … (Cont’d) – G-Man Says 'Reinstate Monica' Aug 27 '22 at 08:54
  • (Cont’d) …  (4) You mention arithmetic expressions. You might want to note that the rules for that vary between Bash versions, and are (arguably) inconsistent within some versions. See the comments here. … (Cont’d) – G-Man Says 'Reinstate Monica' Aug 27 '22 at 08:54
  • (Cont’d) …  (5) You say “An unquoted variable … can be useful … when you know that the value doesn't contain any wildcard character … and you want to split it at whitespace ….” You might want to link to my answer to Security implications of forgetting to quote a variable in bash/POSIX shellsBut what if …? … (Cont’d) – G-Man Says 'Reinstate Monica' Aug 27 '22 at 08:55
  • (Cont’d) …  (6) I’m completely mystified by this “when a raw string is expected” / “when a raw string is expected by the parser” dichotomy.  I don’t have a question *about* it; I literally do not understand what’s being said, and, especially, the distinction between the two variants.  Can you expand on that enough to facilitate comprehension by people who aren’t smarter than I? – G-Man Says 'Reinstate Monica' Aug 27 '22 at 08:55
  • @G-ManSays'ReinstateMonica' (Wow, that's a lot for comments, some of it would be more suitable as questions on the specific aspect.) (1) Your example shows scalar assignments (which happen to be assigning to array elements). There's also the bash (and zsh) syntax array=([INDEX]=ELEMENT), that one is actually assigning ELEMENT as a scalar despite being inside an array element, but I'm not sure about adding it to this answer which is already long. No idea what you're on about, “left/right-hand side” (commonly abbreviated LHS/RHS) is common terminology. – Gilles 'SO- stop being evil' Aug 27 '22 at 09:13
  • @G-ManSays'ReinstateMonica' (2) Again, long answer, plus not really related to the question. (3) IIRC ksh, zsh and bash all behave slightly differently with =~ and quoting, it could be a thread on its own. (4) My answer already states that double quotes inside arithmetic expressions are problematic in “some shells”, I don't see the need to get into details. I could link to a post (not a comment thread). (5) I could link to that thread for the dangers of doing it wrong (but that's kind of the question's purview anyway). Why your answer specifically? – Gilles 'SO- stop being evil' Aug 27 '22 at 09:21
  • @G-ManSays'ReinstateMonica' (6) The rest of the answer is an expansion of that sentence. It shows contexts where a string is expected, vs a list or a pattern. – Gilles 'SO- stop being evil' Aug 27 '22 at 09:22
  • @Gilles'SO-stopbeingevil' on section "Zsh": It could be worth clarifying that this part of the answer focuses on/mostly assumes we are dealing with scalar variables, as opposed to arrays; e.g. when you say "you can omit the double quotes most of the time", "$var never expands to multiple words". If $var is an array, the unquoted form typically expands to a separate word for each non-empty element, if a list of words is possible in that context. – Carl Nov 27 '23 at 13:13
  • @Gilles'SO-stopbeingevil' (cont'd) Also wording choice about "when double-quoting is required", "you can omit..." etc. might be a bit misleading for arrays, since it is more about choosing the desired behaviour. E.g. we commonly do arr=(a b ..); for e in $arr; do ..; where only unqoted $arr (or "${(@)arr}"/"$arr[@]") does what we want. Similarly "_The @ [...] sometimes requires double quotes..._" could be slightly misleading since(@)` is typ. used in order to be able to double-quote the expression. * * * It's obvious you know all this; these clarifications could help readers. – Carl Nov 27 '23 at 13:33
  • @Carl Re. scalar variable: good point, thanks. Re. what is desired: well, sure, there's no law of physics that requires double quotes. The point of this question is when you need double quotes to ensure that $var (or variant) is the value of var rather than doing some extra processing. – Gilles 'SO- stop being evil' Nov 28 '23 at 09:49