The argument to that -e
option of rsync
is not a shell command line, it's an rsync
command line. rsync
parses it with its own rules which differ from that of Bourne shells.
Like all shells, it treats space characters as argument separators and like Bourne shells it treats single quotes and double quotes (but not backslash) as quoting operators. It doesn't do any of the shell expansions (like $var
, $(cmd)
, *.txt
, ~user
...).
So to embed an arbitrary argument in that pseudo command line, you could either enclose it double quotes except for the double quote character themselves (which you'd then enclose in single quotes) or enclose them in single quotes except for the single quote character themselves (which you'd then enclose in double quotes).
For example if your ssh key file was /cygdrive/c/John Doe/.ssh/John "Dude" Doe's.rsa
, the argument to -e
should be something like '/cygdrive/c/John Doe/.ssh/John "Dude" Doe'"'"'s.rsa'
or "/cygdrive/c/John Doe/.ssh/John "'"'"Dude"'"'" Doe's.rsa"
.
You could define a dedicated function to do that rsync-quoting like:
# ksh93/bash/zsh syntax:
rsync_quote() {
local arg="$1"
arg=${arg//\'/\'\"\'\"\'}
printf "'%s'\n" "$var"
}
rsync -e "ssh -i $(rsync_quote "$sshkey") -p $dstport" ...
Another option would be to pass a shell as argument to -e
which would then be the one interpreting the ssh
command line. An advantage to that is that that shell could then do the variable expansions.
KEY=$sshkey PORT=$dstport SSH_COMMAND='ssh -i "$KEY" -p "$PORT" "$@"' \
rsync -e "sh -c 'eval \"\$SSH_COMMAND\"' sh" ...
If you run it with strace -fe execve -s 999
, you see it unroll:
execve("/usr/bin/rsync", ["rsync", "-e", "sh -c 'eval \"$SSH_COMMAND\"' sh", "1/", "localhost:2/"], 0x7ffc03b83678 /* 74 vars */) = 0
strace: Process 7208 attached
[pid 7208] execve("/bin/sh", ["sh", "-c", "eval \"$SSH_COMMAND\"", "sh", "localhost", "rsync", "--server", "-e.LsfxC", ".", "2/"], 0x7ffcc6ad0f28 /* 74 vars */) = 0
[pid 7209] execve("/usr/bin/ssh", ["ssh", "-i", "/cygdrive/c/John Doe/.ssh/John \"Dude\" Doe's.rsa", "-p", "2222", "localhost", "rsync", "--server", "-e.LsfxC", ".", "2/"], 0x5651a0c144a8 /* 74 vars */) = 0
link_stat "/Users/me/some" failed: No such file or directory (2)
The single quotes are getting lost somehow. However, if I add spaces like this' $sshkey '
the quotes are not lost and rsync sees the full path padded with spaces, which makes it an invalid path. – Desmond Hume Nov 27 '18 at 06:27bash
? I can use the code in my answer in the defaultbash
3.2.57 on macOS (with the defaultrsync
) as well as in later releases of the shell on other operating systems (OpenBSD and Linux). – Kusalananda Nov 27 '18 at 06:35GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
– Desmond Hume Nov 27 '18 at 06:51Warning: Identity file /Users/me/some path/with spaces/id_rsa not accessible: No such file or directory.
– Kusalananda Nov 27 '18 at 06:53$src $dstuserhost:$dstparent
the same. But when I used"$src" "$dstuserhost:$dstparent"
instead it suddenly worked. Thank you! – Desmond Hume Nov 27 '18 at 07:12