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.
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\n' $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\n" "${myopts[@]}"
Argument: -e
Argument ssh -p 1234
$ # Just for fun, what if we don't quote myopts?
$ printf "Argument: %s\n" ${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!
After variable expansion, the only processing that's done is word splitting and wildcard expansion (and neither of these is done if the variable is inside doublequotes), not quote processing. You need to use eval if you want all steps of command line parsing to be redone:
eval "rsync $MYOPTS 192.168.0.1:/a /a"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With