Search code examples
tclexpectrsync

rsync-path in expect script


I have written a backup script that uses expect to automate rsync.

To make sure all files get backed up, I use rsync's --rsync-path="sudo rsync" option.

#!/bin/bash
set -e

expect <<- DONE
    spawn rsync --rsync-path="sudo\\ rsync" -uav [email protected]:/home/myuser/ /backups/home/myuser
    expect ":"
    send -- "mypassword\r"
    expect eof
DONE

This does not work as intended. I get the following error message:

bash: sudo rsync: command not found
rsync: connection unexpectedly closed (0 bytes received so far) [Receiver]
rsync error: error in rsync protocol data stream (code 12) at io.c(226) [Receiver=3.1.1]

I have seen similar questions with respect to using spaces in a rsync command line, and have added single quotes, double quotes, and escape backslashes here and there, but nothing has worked yet.

How do I make "--rsync-path with spaces" work within an expect script?


Solution

  • The problem is that you've got this:

    --rsync-path="sudo\\ rsync"
    

    Inside Expect/Tcl, this is seen as:

    --rsync-path="sudo rsync"
    

    And, because Tcl's quoting rules are not the same as bash's, that then uses "sudo rsync" with the double quotes as the command to send to the remote side. Which confuses things terribly. The correct fix is to omit the double quotes; the (backslash-quoted) backslash will ensure that it all gets into spawn as one argument, and gets sent to the other side correctly.


    I really don't like using HEREdocs with Tcl. Too many things can go weird when different sorts of quoting interact. It's much better to use a single script in the real target language, since then you can use variables to make things clearer:

    #!/usr/bin/env expect
    
    set remoteRsync "sudo rsync"
    set from [email protected]:/home/myuser/
    set to   /backups/home/myuser
    set pass "mypassword"
    
    spawn rsync --rsync-path=$remoteRsync -uav $from $to
    expect ":"
    send -- "$pass\r"
    expect eof
    exit
    

    This makes the structure of the code much simpler to see, and easier to debug. The bit with /usr/bin/env at the start is just a way to avoid having the bash wrapper.

    And no, those variables won't need quoting at use. Tcl is not bash.