How do I get the output of a command in Rust?

fn main()
{
    let mut input = Command::new("sh");

    input.arg("-c").arg("wmctrl -d");
    input.output().expect("Error!");
}

I am trying to execute wmctrl -d. I get this output when I execute it on terminal.

0  * DG: N/A  VP: 0,0  WA: N/A  One
1  - DG: N/A  VP: 0,0  WA: N/A  Two
2  - DG: N/A  VP: 0,0  WA: N/A  Three

How would I store this inside the input variable?

Store the result of input.output().expect("Error!") in a variable then read its stdout parameter.

Commands write streams of bytes to their stdout, so you'll need to use String::from_utf8() to convert it to a string.

1 Like

You're almost there. Command::output returns a std::process::Output, which has the field stdout: Vec<u8>. Take that field, and pass it to String::from_utf8, and you'll have the output as a string.

1 Like

Sorry guys I get what you are saying but I am not too sure how to implement this in code wise. I am trying to do it but I keep getting errors.

Edit: I wrote it as: input_string = String::from_utf8(input).unwrap(); I tried other variations still having errors.

What errors are you getting? If you paste the errors here we'll be able to point you in the right direction.

You can also use The Playground to prototype your code and use the "share" button to get a link you can paste here.

You may also want to look at the docs for std::process::Stdio::piped(). It contains a similar example, plus you'll be able to click around the docs and explore what's available.

This is the error I get:

error[E0308]: mismatched types
  --> src/main.rs:15:46
   |
15 |     let mut input_string = String::from_utf8(input).unwrap();
   |                                              ^^^^^ expected struct `std::vec::Vec`, found struct `std::process::Command`
   |
   = note: expected struct `std::vec::Vec<u8>`
              found struct `std::process::Command`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `bar`.

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

I will take a look at that.

You forgot to store the result of input.output().unwrap() in a variable. From that error message it seems like you are trying to convert the Command being built up into a String, which isn't going to work.

I was thinking something closer to this:

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

fn main() {
    let output = Command::new("ls")
        // Tell the OS to record the command's output
        .stdout(Stdio::piped())
        // execute the command, wait for it to complete, then capture the output
        .output()
        // Blow up if the OS was unable to start the program
        .unwrap();

    // extract the raw bytes that we captured and interpret them as a string
    let stdout = String::from_utf8(output.stdout).unwrap();

    println!("{}", stdout);
}

(playground)

Running on the playground gives me something like this:

--- Standard Error ---

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.59s
     Running `target/debug/playground`

--- Standard Output ---

Cargo.lock
Cargo.toml
crate-information.json
src
target
tools

Does that code make sense?

3 Likes

It nearly does makes sense,

I just have a few questions in regards to this just so I understand a bit better in what is going on.

This is my code: let mut input = Command::new("sh"); input.arg("-c")
This is your code: let output = Command::new("ls").

How come you are not using 'sh' inside the Command::new() like I did? As I was told this sh tells the compiler that I want to use a standard posix compliant command? How would ls work without using sh?
Also how come you don't need to specify this input.arg("-c")? How are you able to directly use ls?

Why do I need .stdout(Stdio::piped()) if .output() already captures the output?

With Stdio::piped() inside stdout() what does it do exactly?

.unwrap(); I thought this is used to ignore the output of if the command executed or not correctly?

There are also some crates available that can make your life easier. For example this one:

https://github.com/synek317/shellfn

1 Like

The string you provide to Command::new() is as if you would type in your terminal directly which means you can directly use command line programs like ls. What you did was basically opening up another shell which works but is not necessary.

Yes, this is a little bit redundant but often things are easier to understand if they are explicitly written down.

What Command under the hood does on linux is calling fork() and then call some flavor of exec(). When you fork a process you can chose how to proceed with file descriptors. The default behavior (on linux I think) is that the fds are inherited, so your forked process writes to the same stdout as your original. Stdio::piped() changes this by opening a new pipe() and sets the forked process's stdout to it.

No, unwrap() takes either a Result or an Option if the value is either Result::Ok() or Option::Some() the inner value is returned - in this case the output - if the value is Result::Err() or Option::None your program panics.


In general Command is about system calls related to processes. For further reading I recommend the man pages of fork(), exec() and pipe().

2 Likes

Interesting I will have a look :slight_smile:

Ah I see.

WHen you say fork() do you mean a process is cloned?

Ah I see.

Thanks mate.

Kind of. In Linux each process has its own address space so one process can't easily violate the memory of another process. Indeed, if you fork() a process the whole memory space is copied BUT in a copy-on-write manner. Meaning as long as your new process doesn't write to any memory no copying should be done. The link of memory is removed when exec() starts a executing a new program except for file descriptors, e.g. stdout.
You see between fork and exec you could really mess up your processes. Luckily Rust has this abstracted for us in the Command type.

1 Like

Ok so I am now more familiar with the terms fork(), exec() and pipe.

So like when I call Command::new("ls") this forks the process? Why does it need to fork it?

It’s been this way since the early days of UNIX. The operating system gets one userspace process running at boot, and all others are made by copying one that already exists. Though they’re generally used together these days, fork and exec also have some use on their own:

  • fork can get another copy of your program running concurrently, and the two copies can talk to each other via their shared file descriptors.
  • exec hands off control to some other program, so that you don’t have to write everything into a single binary.

So when I call Command::new("ls") this forks the Rust's own program and then it runs an exec on it to replace the Rust's program with the ls program on the forked program?

Yes, that’s exactly right.

2 Likes

Ok glad I understand that bit now :slight_smile:

.stdout(Stdio::piped()) .output() with this function what does it do, I am still a bit confused. Does stdout() tell the forked process output the result onto stdout? But then what does output() do?

  1. There are three function calls in here, not one.
  2. Quoting the documentation on Command::stdout:

Configuration for the child process's standard output (stdout) handle.

To understand what it means, we have to look on the argument.

  1. Quoting the documentation on Stdio::piped:

A new pipe should be arranged to connect the parent and child processes.

So, the program (maybe your program, maybe the OS - that's irrelevant for now) will construct the pipe and redirect the child process standard output to that pipe.

  1. Quoting the documentation on Command::output:

Executes the command as a child process, waiting for it to finish and collecting all of its output.

  1. If something is not clear, please quote the relevant parts of documentation explicitly. It's hard to help you if we don't know what you've done yourself.
3 Likes

THanks for the links, I nearly understand all of it now.

Describes what to do with a standard I/O stream for a child process when passed to the stdin , stdout , and stderr methods of Command .

So I now understand what Stdio::piped does. The only thing I am curious to know is that when doing this:

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

let output = Command::new("echo")
    .arg("Hello, world!")
    .stdout(Stdio::piped())
    .output()
    .expect("Failed to execute command");

Is this stdout() established in the child process or the parent process?