Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

bash - Quotes inside a variable are ignored

I want to pass an argument to rsync from a variable like this:

myopts='-e "ssh -p 1234" -a'
rsync $myopts 192.168.0.1:/a /a

For some reason this does not work. I even found this webpage where it says that it won't work:

### NO NO NO: this passes three strings:
###      (1)  "my
###      (2)  multiword
###      (3)  argument"
MYARG=""my multiword argument""
somecommand $MYARG

### THIS IS NOT (!!!!) THE SAME AS ###
command "my multiword argument"

### YOU NEED ###
MYARG="my multiword argument"
command "$MYARG"

Unfortunately it doesn't say why it won't work.

The script containing the rsync call is used by a lot of other scripts, so I can only change it in a compatible way. A solutions that doesn't work is using an array:

myopts=('-a' '-v' '-z') # new way, would work
myopts='-a -v -z'       # old way, breaks
rsync "${myopts[@]}" 192.168.0.1:/a /a

The options from the variable are completely ignored.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It has to do with the way Bash splits the argument. Now, never, ever put commands and there arguments in one variable. Use arrays instead (and also, stop using uppercase variable names, it's ugly and dangerous as it may clash with already defined variables). You should instead write:

myopts=( -e "ssh -p 1234" )
rsync "${myopts[@]}" 192.168.0.1:/a /a

In this case, rsync will be launched with the following arguments:

-e
ssh -p 1234
192.168.0.1:/a
/a

which is probably what you want.


As to why it works this way: assume that

myopts='-e "ssh -p 1234"'

that is, myopts is the following string:

-e "ssh -p 1234"

Unquoted, Bash will see four tokens. You can check it thus: in a terminal do:

$ myopts='-e "ssh -p 1234"'
$ printf 'Token: %s
' $myopts
Token: -e
Token: "ssh
Token: -p
Token: 1234"

so when you launch

rsync $myopts 192.168.0.1:/a /a

then it's like launching rsync with these arguments:

-e
"ssh
-p
1234"
192.168.0.1:/a
/a

and you probably don't want that :)

Now, the variable $myopts quoted, is just one token:

-e "ssh -p 1234"

so if you launch (observe the quotes):

rsync "$myopts" 192.168.0.1:/a /a

it's like launching rsync with these arguments:

-e "ssh -p 1234"
192.168.0.1:/a
/a

and you don't want that either.

Really, the most robust way to solve this problem is to use Bash arrays. Look: define an array myopts as (do it in a terminal):

$ myopts=( -e "ssh -p 1234" )
$ # myopts is an array with two arguments. Check this (observe the quotes):
$ printf "Argument: %s
" "${myopts[@]}"
Argument: -e
Argument ssh -p 1234
$ # Just for fun, what if we don't quote myopts?
$ printf "Argument: %s
" ${myopts[@]}
Argument: -e
Argument: "ssh
Argument: -p
Argument 1234"

So now, I guess that you understand why this:

rsync "${myopts[@]}" 192.168.0.1:/a /a

is like launching rsync with these arguments:

-e
ssh -p 1234
192.168.0.1:/a
/a

… and that's probably what you want :).

Hope this helps!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...