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.
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.
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
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);
}
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?
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().
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.
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?
.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?
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.
Executes the command as a child process, waiting for it to finish and collecting all of its output.
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.
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?