使用 std::process::Command 捕获并继承 stdout 和 stderr

Capture and inherit stdout and stderr using std::process::Command

使用 Rust 很容易 运行 继承 stdoutstderr 捕获它们的命令。有什么方法可以同时(实时)完成这两项工作吗?

如果你抛开细节,很容易在简短的评论中描述如何做到这一点——你做 tee 做的事;每个 read() 两个 write()。但细节才是难点。

这是一个使用 Crossbeam 的作用域线程的完整解决方案。

use std::io::{self, Write};
use std::process::{Command, Stdio, ExitStatus};

use crossbeam::thread;

struct TeeWriter<'a, W0: Write, W1: Write> {
    w0: &'a mut W0,
    w1: &'a mut W1,
}

impl<'a, W0: Write, W1: Write> TeeWriter<'a, W0, W1> {
    fn new(w0: &'a mut W0, w1: &'a mut W1) -> Self {
        Self {
            w0,
            w1,
        }
    }
}

impl<'a, W0: Write, W1: Write> Write for TeeWriter<'a, W0, W1> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        // We have to use write_all() otherwise what happens if different
        // amounts are written?
        self.w0.write_all(buf)?;
        self.w1.write_all(buf)?;
        Ok(buf.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        self.w0.flush()?;
        self.w1.flush()?;
        Ok(())
    }
}

pub fn run_and_capture(command: &mut Command) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> {

    command.stdout(Stdio::piped());
    command.stderr(Stdio::piped());
    let mut child = command.spawn()?;
    // These expects should be guaranteed to be ok because we used piped().
    let mut child_stdout = child.stdout.take().expect("logic error getting stdout");
    let mut child_stderr = child.stderr.take().expect("logic error getting stderr");

    thread::scope(|s| {
        let stdout_thread = s.spawn(|_| -> io::Result<Vec<u8>> {
            let stdout = io::stdout();
            let mut stdout = stdout.lock();
            let mut stdout_log = Vec::<u8>::new();
            let mut tee = TeeWriter::new(&mut stdout, &mut stdout_log);
            io::copy(&mut child_stdout, &mut tee)?;
            Ok(stdout_log)
        });
        let stderr_thread = s.spawn(|_| -> io::Result<Vec<u8>> {
            let stderr = io::stderr();
            let mut stderr = stderr.lock();
            let mut stderr_log = Vec::<u8>::new();
            let mut tee = TeeWriter::new(&mut stderr, &mut stderr_log);

            io::copy(&mut child_stderr, &mut tee)?;
            Ok(stderr_log)
        });

        let status = child.wait().expect("child wasn't running");

        let stdout_log = stdout_thread.join().expect("stdout thread panicked")?;
        let stderr_log = stderr_thread.join().expect("stderr thread panicked")?;

        Ok((status, stdout_log, stderr_log))
    }).expect("stdout/stderr thread panicked")
}

#[cfg(test)]
mod test {
    use super::run_and_capture;
    use std::process::Command;

    #[test]
    fn test_echo() {
        let mut command = Command::new("echo");
        command.arg("hello");
        let (_status, stdout, stderr) = run_and_capture(&mut command).unwrap();
        assert_eq!("hello\n".as_bytes(), stdout);
        assert_eq!(stderr, &[]);
    }
}

它经过了最低限度的测试。我不是 100% 了解所有 .expect(),因为一些文档不清楚会发生什么错误(例如 child.wait())。