使用 std::process::Command 捕获并继承 stdout 和 stderr
Capture and inherit stdout and stderr using std::process::Command
使用 Rust 很容易 运行 继承 stdout
和 stderr
或 捕获它们的命令。有什么方法可以同时(实时)完成这两项工作吗?
如果你抛开细节,很容易在简短的评论中描述如何做到这一点——你做 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()
)。
使用 Rust 很容易 运行 继承 stdout
和 stderr
或 捕获它们的命令。有什么方法可以同时(实时)完成这两项工作吗?
如果你抛开细节,很容易在简短的评论中描述如何做到这一点——你做 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()
)。