Search code examples
shellrustcommand-line-argumentsexecute

How do I execute a command with quotation marks in an argument?


I'm trying to run the command

rsync -rtv -e 'ssh -i /Users/yuanlinfeng/.ssh/id_rsa -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking no" -o "ConnectTimeout=2"' /tmp/a username@ip:/home/ubuntu/a

The code is:

let login_settings = format!(r#"'ssh -i {} -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking no" -o "ConnectTimeout=2"'"#, identity);
let mut cmd = Command::new("rsync");
cmd.env("SETTING", login_settings);
cmd.arg("-rtv").arg("-e").arg(login_settings);
if delete {
    cmd.arg("--delete");
}
match exclude_files {
    None => {}
    Some(exclude_files) => {
        for file in exclude_files.iter() {
            cmd.arg("--exclude").arg(file);
        }
    }
}
let target = format!("{}@{}:{}", username, remote_ip, dest);
cmd.arg(source).arg(target);
let output = cmd.output()?;
if output.stdout.len() > 0 {
    println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
}
if output.stderr.len() > 0 {
    println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}

The error:

stderr: rsync: Failed to exec ssh -i /Users/yuanlinfeng/.ssh/id_rsa -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking no" -o "ConnectTimeout=2": No such file or directory (2)
rsync error: error in IPC code (code 14) at /BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-52/rsync/pipe.c(86) [sender=2.6.9]
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
rsync error: error in rsync protocol data stream (code 12) at /BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-52/rsync/io.c(453) [sender=2.6.9]

It looks like the quotation marks in the arguments are removed. How do I fix it?

I'm using macOS High Sierra and Rust 1.22.1.


Solution

  • The ' quotes around the ssh ... argument are not actually passed to rsync; they are used by the shell to separate the arguments to rsync.

    Your Rust code tries to call something like this: rsync -e "'ssh ...'" .... rsync then tries to split the 'ssh ...' argument similar to the shell (it seems it doesn't actually use the shell to expand it). This means it will see ssh ... as one argument (the filename to execute) but such a file doesn't exist.

    Remove the ' quotes in your format! call, and it might work.

    On Linux you could run your example through strace -ff -e trace=execve -- ./target/... to see how this works. macOS has dtruss, but I can't help you with that :)