[英]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 可以很容易地运行继承
stdout
和stderr
或捕获它们的命令。 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.