0

In my script I've got:

sshkey="/Users/me/some path/with spaces/id_rsa"
dstport=...
dstparent=...
dstuserhost=...

rsync -az --delete -e "ssh -i $sshkey -p $dstport" $src $dstuserhost:$dstparent

And when I run it, I get:

rsync: link_stat "/Users/me/some" failed: No such file or directory (2)

Desmond Hume
  • 2,798

3 Answers3

1

You need to inject some "inner" quotes. I would say

printf -v rsh_cmd 'ssh -i "%s" -d "%s"' "$sshkey" "$dstport"
...
rsync ... -e "$rsh_cmd"
glenn jackman
  • 85,964
1

Simply adding single quotes around $sshkey would solve it:

rsync -az --delete -e "ssh -i '$sshkey' -p '$dstport'" "$src" "$dstuserhost:$dstparent"

The $sshkey value would be expanded by your interactive shell before calling rsync because it's within a double quoted string, but the single quotes would stop the string from being further split when rsync calls ssh to set up the connection.

That assumes $sshkey doesn't contain single quote characters itself.

Kusalananda
  • 333,661
  • Doesn't work: rsync: 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:27
  • @DesmondHume Are you running bash? I can use the code in my answer in the default bash 3.2.57 on macOS (with the default rsync) as well as in later releases of the shell on other operating systems (OpenBSD and Linux). – Kusalananda Nov 27 '18 at 06:35
  • GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18) – Desmond Hume Nov 27 '18 at 06:51
  • @DesmondHume When I'm testing on macOS, the message I get is a warning that I don't have the file (since I don't): Warning: Identity file /Users/me/some path/with spaces/id_rsa not accessible: No such file or directory. – Kusalananda Nov 27 '18 at 06:53
  • This is weird. I added single quotes but kept the unrelated $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
0

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