Run users command with arguments and return output

Looking some help to make it work, after sending almost 2 hrs. compiler errors are completely annoying and making no sense to me.

I want to execute a command provided by user and return its output, which takes me 2 mins to write in java and 30 seconds in kotlin.

use std::process::{Command, Stdio};

fn main(){
    let command = run_command("dir",vec![""]);
    println!("{}", command)
}

 fn run_command(cmd: &str, args: Vec<&str>) -> String {
    let mut cmd_with_args = cmd.to_owned();
    let x = args.join(" ");
    cmd_with_args.push_str(&*x);
    cmd_with_args.trim();
    let output = if cfg!(target_os = "windows") {
        Command::new("cmd")
            .args(&["/C", &*cmd_with_args])
    } else {
        Command::new("sh")
            .arg(&["-c", &*cmd_with_args])
    }
        .stdout(Stdio::piped())
        .output()
        .unwrap()
        .stdout;
    String::from_utf8(output).unwrap()
}

Also how can i make this more readable.

use std::process::{Command, Stdio};

fn main() {
    let command = run_command("ls", &vec![""]);
    println!("{}", command)
}

fn run_command(program: &str, args: &[&str]) -> String {
    fn init(shell: &str, arg: &str) -> Command {
        let mut cmd = Command::new(shell);
        cmd.arg(arg);
        cmd
    }
    let mut cmd = if cfg!(target_os = "windows") {
        init("cmd", "/C")
    } else {
        init("sh", "-c")
    };
    let output = cmd
        .arg(program)
        .args(args)
        .stdout(Stdio::piped())
        .output()
        .unwrap()
        .stdout;
    String::from_utf8(output).unwrap()
}

You should really avoid the little snide remarks you make in every post. You're asking for help, try to be agreeable.

2 Likes

I didn't try this on Windows, but this is what I came up with

use std::process::Command;

fn main() {
    let output = run_command("dir", &[""]);
    println!("{}", output)
}

fn run_command(cmd: &str, args: &[&str]) -> String {
    let (exe, flag) = if cfg!(target_os = "windows") {
        ("cmd", "/C")
    } else {
        ("sh", "-c")
    };
    let output = Command::new(exe)
        .arg(flag)
        .arg(cmd)
        .args(args)
        .output()
        .unwrap()
        .stdout;
    String::from_utf8(output).unwrap()
}

As for what issues the original code had, the Windows code used args but other platforms used arg, leading to an error that tries to say that an array cannot be used as a single argument:

error[E0277]: the trait bound `[&str; 2]: AsRef<OsStr>` is not satisfied
  --> src/main.rs:16:32
   |
16 |         Command::new("sh").arg(&["-c", &*cmd_with_args])
   |                                ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `AsRef<OsStr>` is not implemented for `[&str; 2]`
   |
   = help: the following implementations were found:
             <[T; N] as AsRef<[T]>>
             <[T] as AsRef<[T]>>
   = note: required because of the requirements on the impl of `AsRef<OsStr>` for `&[&str; 2]`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `sandbox-rs`

To learn more, run the command again with --verbose.

The arg call tries to turn its argument into a string (OsStr, to be precise), but it can't do that with an array. That fixed, the next error is

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:14:9
   |
13 |       let output = if cfg!(target_os = "windows") {
   |  __________________-
14 | |         Command::new("cmd").args(&["/C", &*cmd_with_args])
   | |         ^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
15 | |     } else {
   | |     - temporary value is freed at the end of this statement
16 | |         Command::new("sh").args(&["-c", &*cmd_with_args])
17 | |     }
   | |_____- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:16:9
   |
13 |       let output = if cfg!(target_os = "windows") {
   |  __________________-
14 | |         Command::new("cmd").args(&["/C", &*cmd_with_args])
15 | |     } else {
16 | |         Command::new("sh").args(&["-c", &*cmd_with_args])
   | |         ^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
17 | |     }
   | |     -
   | |     |
   | |_____temporary value is freed at the end of this statement
   |       borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0716`.
error: could not compile `sandbox-rs`

To learn more, run the command again with --verbose.

What this error is saying that the Command you're making is dropped at the end of the if-block, but you're still trying to use it after that. The problem is that the args method takes and returns a mutable borrow of a command, not a full command struct. The error may make more sense if we write the if block out like this

    let output = if cfg!(target_os = "windows") {
        let mut command = Command::new("cmd");
        let mutable_borrow_of_command = command.args(&["/C", &*cmd_with_args]);
        mutable_borrow_of_command
        // command is dropped here...
    } else {
        let mut command = Command::new("sh");
        let mutable_borrow_of_command = command.args(&["-c", &*cmd_with_args]);
        mutable_borrow_of_command
         // command is dropped here...
    }
    .stdout(Stdio::piped()) // but we're trying to keep using it here
    .output()
    .unwrap()
    .stdout;

This is functionally the same as your code, but hopefully makes the issue more clear.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.