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?
-
"Is there any reason not to quote variables?" - They tell fibsEd Heal– Ed Heal2015年09月20日 02:37:36 +00:00Commented Sep 20, 2015 at 2:37
-
Generally in for-loops when you need word splitting on the result of a non-array variable expansion.Todd A. Jacobs– Todd A. Jacobs2015年09月20日 02:38:38 +00:00Commented Sep 20, 2015 at 2:38
4 Answers 4
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)
1 Comment
[[ 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 [[ ]].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.
1 Comment
COUNT=1 instead of COUNT="1", but I would always default to evaluating "$COUNT", simply out of habit.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.
Comments
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.