Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would you stream output from a Process?

Tags:

rust

I believe I understand, in general, one way of doing this:

  • Create a Command
  • Use Stdio::piped() to create a new pair of output streams
  • Configure command.stdout(), and command.stderr()
  • Spawn the process
  • Create a new thread and pass the stderr and stdout to it <-- ???
  • In the remote thread, continually poll for input and write it to the output stream.
  • In the main thread, wait for the process to finish.

Does that sound right?

My two actual questions:

  1. Is there an easier way that doesn't involve a 'read thread' per process?

  2. If there isn't an easier way, Read::read() requires &mut self; how do you pass that into a remote thread?

Please provide specific examples of how to actually stream the output, not just generic advice about how to do it...

To be more specific, here's the default example of using spawn:

use std::process::Command;

let mut child = Command::new("/bin/cat")
                        .arg("file.txt")
                        .spawn()
                        .expect("failed to execute child");

let ecode = child.wait()
                 .expect("failed to wait on child");

assert!(ecode.success());

How can the above example be changed to stream the output of child to the console, rather than just waiting for an exit code?

like image 888
Doug Avatar asked Aug 13 '15 15:08

Doug


2 Answers

Although the accepted answer is correct, it doesn't cover the non-trivial case.

To stream output and handle it manually, use Stdio::piped() and manually handle the .stdout property on the child returned from calling spawn, like this:

use std::process::{Command, Stdio};
use std::path::Path;
use std::io::{BufReader, BufRead};

pub fn exec_stream<P: AsRef<Path>>(binary: P, args: Vec<&'static str>) {
    let mut cmd = Command::new(binary.as_ref())
        .args(&args)
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

    {
        let stdout = cmd.stdout.as_mut().unwrap();
        let stdout_reader = BufReader::new(stdout);
        let stdout_lines = stdout_reader.lines();

        for line in stdout_lines {
            println!("Read: {:?}", line);
        }
    }

    cmd.wait().unwrap();
}

#[test]
fn test_long_running_process() {
    exec_stream("findstr", vec!("/s", "sql", "C:\\tmp\\*"));
}

See also Merge child process stdout and stderr regarding catching the output from stderr and stdout simultaneously.

like image 178
Doug Avatar answered Oct 09 '22 00:10

Doug


I'll happily accept any example of spawning a long running process and streaming output to the console, by whatever means.

It sounds like you want Stdio::inherit:

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

fn main() {
    let mut cmd =
        Command::new("cat")
        .args(&["/usr/share/dict/web2"])
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .spawn()
        .unwrap();

    // It's streaming here

    let status = cmd.wait();
    println!("Exited with status {:?}", status);
}
like image 39
Shepmaster Avatar answered Oct 09 '22 01:10

Shepmaster