简体   繁体   English

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

[英]Capture and inherit stdout and stderr using std::process::Command

With Rust it's very easy to run a command that either inherits stdout and stderr OR captures them.使用 Rust 可以很容易地运行继承stdoutstderr捕获它们的命令。 Is there a way I can do both simultaneously (and in real-time)?有没有一种方法可以同时(实时)做这两件事?

If you put aside the details it's easy to describe how to do this in a short comment - you do what tee does;如果你抛开细节,很容易在简短的评论中描述如何做到这一点——你做tee做的事; two write() s for each read() .每个read()有两个write() ) 。 But the details are the difficult part.但细节是困难的部分。

Here's a complete solution using Crossbeam's scoped threads.这是使用 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, &[]);
    }
}

It's minimally tested.它经过了最低限度的测试。 I'm not 100% about all the .expect() s because some of the documentation is not clear about what errors can happen (eg child.wait() ).我不是 100% 了解所有.expect() s,因为一些文档不清楚会发生什么错误(例如child.wait() )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM