Is there any reason not to quote variables?

旧街凉风 提交于 2021-02-16 13:28:32

问题


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?


回答1:


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)




回答2:


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.




回答3:


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.




回答4:


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.



来源:https://stackoverflow.com/questions/32674809/is-there-any-reason-not-to-quote-variables

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!