Looking for crate for easier handling of subprocess output

The provided std::process::Command approach lets me start a command and have it output to a terminal as normal, be stored and provided at subprocess termination, OR be handled manually as it comes in. Are there any convenience crates for doing multiple of these at once, but easily?

I could code it myself (probably some sort of trait providing a function like better_spawn(self, terminal_output: bool, capture_output: bool, stream_output: bool) -> (std::process::Child, impl Read, impl Read), but I figure there's like a 90% chance someone else has already done it and I can save the hassle.

fn main() {
    // only visible at process end
    let out1 = std::process::Command::new("echo").arg("asdf").output();
    dbg!(&out1);

    // Not captured at all
    let out2 = std::process::Command::new("echo")
        .arg("asdf")
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .unwrap()
        .wait();
    dbg!(&out2);
    
    // Easy to pass to other threads, but no automatic printing or collection
    let mut child3 =  std::process::Command::new("echo")
        .arg("asdf")
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .unwrap();
    let mut stdout3 = child3.stdout.take().unwrap();
    let mut stderr3 = child3.stderr.take().unwrap();
    use std::io::Read;
    let mut s_out3 = std::string::String::new();
    stdout3.read_to_string(&mut s_out3).unwrap();
    dbg!(&s_out3);
    let mut s_err3 = std::string::String::new();
    stderr3.read_to_string(&mut s_err3).unwrap();
    dbg!(&s_err3);
    let out3 = child3.wait();
    dbg!(&out3);
}


(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.85s
     Running `target/debug/playground`
[src/main.rs:4:5] &out1 = Ok(
    Output {
        status: ExitStatus(
            unix_wait_status(
                0,
            ),
        ),
        stdout: "asdf\n",
        stderr: "",
    },
)
[src/main.rs:14:5] &out2 = Ok(
    ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
)
[src/main.rs:28:5] &s_out3 = "asdf\n"
[src/main.rs:31:5] &s_err3 = ""
[src/main.rs:33:5] &out3 = Ok(
    ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
)

This may be an option, although it needs Tokio (which understandable as per your request):

tokio-process-tools

You still need to println it yourself but it enables you to do it in real time per line.

Splitting a pipe into two is not an operation natively supported in POSIX, nor in any *nix that I know of. You could absolutely implement it though if you need it, by copying data into a buffer as you read it.

For pipelines in shell there is already tee that does this. But outside of that it seems rather niche to me, so I don't know of any library that provides this.

It would seem like a natural extension to add to the duct crate though.

2 Likes