5

Is there any valid reason not to put all variables in a shell script in quotes? ("Not knowing any better" is not a valid reason in my opinion.)

Generally speaking, quoting variables makes sure that they are seen as one variable, should they contain spaces or other special characters. So why would anyone ever go for an "unsafe" way and not quote variables?

asked Sep 20, 2015 at 1:17
2
  • "Is there any reason not to quote variables?" - They tell fibs Commented Sep 20, 2015 at 2:37
  • Generally in for-loops when you need word splitting on the result of a non-array variable expansion. Commented Sep 20, 2015 at 2:38

4 Answers 4

13

So why would anyone ever go for an "unsafe" way and not quote variables?

That is almost never the right thing to do. Newcomers to shell are almost always better served by placing variables in double-quotes. For advanced users, I can think of three possible exceptions:

1. You want pathname expansion.

An example could be:

glob=*.xml
# Do something, possibly creating xml files
rm $glob

Here, pathname expansion is performed on glob not when it is defined but when rm is executed. There might be times when such late evaluation is beneficial.

For bash, one can can create an array with glob=(*.xml). This is likely superior for every case except when late evaluation is important.

2. You want word splitting.

An example is handling simple command options when you want compatibility with POSIX shells that that don't support arrays:

opts=
[ -f "$onething" ] && opts="-a"
[ -f "$another" ] && opts="$opts -b"
cmd $opts

Here, word splitting allows the command cmd to see multiple options. If $opts were in quotes, it would see only one string.

This approach for multiple options is limited to simple options. For bash, one would replace the variable opts with an array which works much better when options are more complex. POSIX shells, however, do not support named arrays so using a variable like this is viable as a second-best work-around.

3. Bash's [[

Inside [[...]], you may sometimes want the operator = to test for a glob match. If so the right-hand side must be unquoted:

$ glob=*.xml
$ [[ file.xml = "$glob" ]] && echo yes || echo no
no
$ [[ file.xml = $glob ]] && echo yes || echo no
yes

Glob matches are handy when you want them. If you weren't expecting them, they can be a disaster. Thus, unless you explicitly want a glob match, the right-hand side must be quoted.

Likewise if you want the =~ operation to test for a regex match:

$ regex=fi.*ml
$ [[ file.xml =~ "$regex" ]] && echo yes || echo no
no
$ [[ file.xml =~ $regex ]] && echo yes || echo no
yes

(Hat tip: Gordon Davisson)

glenn jackman
249k42 gold badges233 silver badges363 bronze badges
answered Sep 20, 2015 at 2:02
Sign up to request clarification or add additional context in comments.

1 Comment

One (two?) more cases: if you have a glob pattern or regular expression in a variable, and want to use it in a bash [[ something = $globpattern ]] or [[ something =~ $regex ]], you must leave the variable unquoted or it'll be interpreted literally instead of as a pattern/regex. But note that if you want to use [[ $variable1 = $variable2 ]] to test plain string equality rather than pattern match, you must double-quote at least the right-hand-side of the comparison. Note that if you don't understand the distinction I'm making, you should probably be double-quoting even in [[ ]].
1

Well, to me "reducing unnecessary syntactic noise to make the script more readable">>could be<< a valid reason, at least in some peoples' minds.

I'm assuming that there are situations where the variable CANNOT have spaces in them; e.g.

 COUNT=1
 FILENAME=file$COUNT.txt

and that are other cases where it really doesn't matter how the embedded spaces are treated; e.g.

 echo the filename is $NAME 

But if you don't accept that as "valid" (and I think you won't from the tone of your question), then I guess the answer is No.


And obviously, there are situations where quoting would suppress behavior that you actually require; e.g. tokenization or globbing.

answered Sep 20, 2015 at 1:31

1 Comment

I think a lot of people use COUNT=1 instead of COUNT="1", but I would always default to evaluating "$COUNT", simply out of habit.
1

TL;DR

The main use case for unquoted variables is for-loops that require word splitting on the result of a non-array variable expansion. Globs should generally be unquoted, as well.

When Not to Quote an Expansion

If you have data in an array, you generally want to expand using quoted-array expansion with the special @ subscript. For example:

foo=(bar baz quux)
for word in "${foo[@]}"; do
 echo "$word"
done

However, if you have data in a non-array variable (e.g. PATH or MANPATH) and want to iterate, the following wouldn't work if you quoted the ${MANPATH//:/ } expansion:

MANPATH=/usr/share/man:/usr/local/share/man
# Replace colons with spaces to create list.
for path in ${MANPATH//:/ }; do
 echo "$path"
done

By not quoting the MANPATH expansion, word-splitting will result in a space-separated list of items to loop over. However, when double-quoted, the expansion of "${MANPATH//:/ }" would result in a single space-separated word rather than a list. This is probably not what you want.

answered Sep 20, 2015 at 3:04

Comments

1

In general, always use: "$variable" when expanding a variable ...
and/or "Command execution": "$( ls )" and, more leniently
on "Arithmetic Expansion": "$(( 2+2 ))".
As it becomes a single number, it may be argued, a safe expansion then.

There are several conditions on which it is argued by some people that a variable expansion does not need to be quoted:

1. On assignment: var=$oldvar.

That is valid even if $oldvar contains spaces, because:
"word splitting is not performed on the expansion of the value of oldvar.

However, an unquoted string with spaces will obviously fail, and it must be quoted:

var=a var with spaces # Clearly will fail.
var="a var with spaces" # It MUST be quoted to work.

It should be (therefore) natural to find assignments like this IMhO:

var="$oldvar"

2. You want/need pathname expansion.

Like in echo $var with var='/home/user/*/' to get a list of directories inside /home/user/.

3. You want/need to remove newlines.

 echo $(ls -d directory/*)

arguably, a very insecure choice in most cases.
But not in very specific ones. As in this one:

 echo $(seq 1 10)

Only numbers, no odd file names, no user input.

4. Allow matching a pattern/regex (not exact strings).

In case statements to match the pattern and not the exact string.

var='*[0-9]*' 
case "1ドル" in
 $var) echo "has one digit (at least)";;

In the right side of =, == or != : [[ $vara == $varb ]].
In the right side of =~ to match the regex and not exact strings.

5. Want or need "Word splitting".

For sure, one of the most dangerous use of unquoted var expansion is to get "word splitting".

As an example, that is quite common in in expansions: for var in $list.

The var list must remain unquoted to get "word splitting". But then, globing is also performed. Any part of $list value that has a free * will be expanded to the list of files in the present directory.

Another simple example is to set positional parameters: set -- $var.
With var='a variable with spaces', 1ドル will be a, 2ドル variable, etc.

If the command executed is set -- "$var", then only one positional parameter will be set.
That is, 1ドル will be 'a variable with spaces'.

Note: This also work for $var values that do not contain - with set - $var. It is, however, a very insecure way of setting positional parameters.

answered Sep 20, 2015 at 5:01

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.