rsync complaining about `missing trailing-"` in a Bash script

Short answer: see BashFAQ #050.

Long answer: the problem you’re having is that embedding quotes in variables doesn’t work the way you think it does. Specifically, quotes are parsed before variables are replaced, so if a variable’s value includes quotes it’s too late for them to work right. When you set RSYNCOPT="--rsh=\"ssh -p 10022 -c des\" ..." and then use ${RSYNCOPT} in a command line, the quotes in the variable don’t get parsed, they’re just treated as normal characters. Thus, rather than the rsync command receiving a single parameter --rsh=ssh -p 10022 -c des, it receives 5: --rsh="ssh, -p, 10022, -c, and des". Since the --rsh command contains a single (unmatched) quote, you get an error.

To see what’s going on better, either use set -x to make the shell print each command before executing it (so you can see what’s really happening), or replace the echo ${whatever} (which is highly misleading) with printf "%q " ${whatever}; echo.

There are several ways to solve this. One is to avoid trying to store RSYNCOPT (and probably other things as well) in variables in the first place. Another is to store RSYNCOPT as an array (which can keep track of word boundaries without any of this confusion) rather than a simple string.

To print the command before executing it, either use set -x before the rsync command and set +x after to turn it off, or use something like the printf command I listed above (note that it prints a stray space after the command, but this generally doesn’t matter).

Here’s the array+printf approach:

...
RSYNCOPT=(--rsh="ssh -p 10022 -c des" --rsync-path=\"/opt/bin/rsync\" --inplace --progress -a -vv)
...
printf "%q " ${SCREEN} ${SCREENOPT} ${SCREEN_TITLE}
echo

printf "%q " ${RSYNC} "${RSYNCOPT[@]}" ${SOURCE} ${REMOTE_USER}${REMOTE_HOST}${REMOTE_BASE}${REMOTE_TARGET}
echo
${RSYNC} "${RSYNCOPT[@]}" ${SOURCE} ${REMOTE_USER}${REMOTE_HOST}${REMOTE_BASE}${REMOTE_TARGET}

The maze of escaped and unescaped quotation marks in your $RSYNCOPT confuses the heck out of me; I'm not surprised that it confuses rsync, and/or ssh, and/or the local or remote shell.

There may be a way to get this to work by adding or removing backslashes, but I suggest the following workaround instead:

Replace:

RSYNCOPT="--rsh=\"ssh -p 10022 -c des\" --rsync-path=\"/opt/bin/rsync\" --inplace --progress -a -vv"

by:

export RSYNC_RSH="ssh -p 10022 -c des"
RSYNCOPT="--rsync-path=\"/opt/bin/rsync\" --inplace --progress -a -vv"

I tried a slightly simplified version of your script on my system and got the same error message you did; this workaround corrected it.


Using the ssh configuration file is also a valid option. Here's what you can put/add to your ~/.ssh/config file:

Port 10022
Cipher des

You can also filter these parameters by remote host by prepending these lines with Host xyz.domain.whatever and indenting them:

Host 192.168.1.15
    Port 10022
    Cipher des