2

I am trying to pass a variable as a part of a bash command in my Rust program.

let path : String = "/home/directory/".to_string();
let  _command = Command::new("/bin/bash")
                  .arg("-c")
                  .arg("mv somefile.txt $path")
                  .stdout(Stdio::piped())
                  .spawn()
                  .expect("Cannot be executed")
                  .wait(); 

This program compiles correctly but the designated variable $path under Command::new() is not identical to the string variable provided above. Would like to know how to pass variables from the Rust environment directly into the argument.

1 Answer 1

4

$path as written would be a shell or environment variable. You can pass Rust variables with arg. The easiest and safest way to do this is to get rid of the bash -c invocation and call mv directly. Then you can pass &path as a separate argument:

let path = "/home/directory/".to_string();
let exit_code = Command::new("mv")
    .arg("somefile.txt")
    .arg(&path)
    .spawn()
    .expect("Cannot be executed")
    .wait()
    .expect("Wait failed");

Playground

If you did need the explicit shell invocation for some reason, the naïve approach might be to use manual string building, say with format!, to build a shell command:

// Unsafe! Do not build commands by hand.
let exit_code = Command::new("sh")
    .arg("-c")
    .arg(&format!("mv somefile.txt {path}"))
    ...

Don't do that! It is unsafe in the same way that building SQL strings by hand leads to SQL injection vulnerabilities. If path were a file name with spaces it would be split into several file names. Worse, an attacker could inject a path like ; rm -rf / and wipe out your server.

The safe way to pass arguments is still using separate arg invocations. The trick is to reference them as "$1", "$2", etc., in the shell command. This method ensures that spaces, quotes, and other tricky characters won't cause problems.

let exit_code = Command::new("sh")
    .arg("-c")
    .arg("mv somefile.txt \"$1\"")
    .arg("sh") // $0
    .arg(&path) // $1
    .spawn()
    .expect("Cannot be executed")
    .wait()
    .expect("Wait failed");

Playground


Also, don't use let _command =. That would assign the final Result to a variable without checking whether it was successful or not. It's a good habit to always check Results. You can see I added a second expect call in both of the snippets above.

But then, calling unwrap and expect too often is another bad habit. Whenever possible you should aim to propagate errors. The ? operator makes this easy. It's shorter and nicer for callers:

fn main() -> io::Result<()> {
    let path = "/home/directory/".to_string();
    let exit_code = Command::new("mv")
        .arg("somefile.txt")
        .arg(&path)
        .spawn()?
        .wait()?;
    Ok(())
}

Playground

Sign up to request clarification or add additional context in comments.

3 Comments

Either that or use format!(..); but that would probably be a lot more error prone. remembers sql exploits
Do you have any idea on how can a similar exercise be followed on the Windows cmd shell? Like given a string argument how do we pass them instead of \"$1\".
I don't know Windows, sorry. If mv is the command you're really using and not just an example, ditch the command line and call std::fs::rename. That'll be portable across operating systems.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.