如何修改CmdRunner实现命令输出的终端实时打印?
如何让Rust CmdRunner实时输出子进程的stdout/stderr?
我实现了一个CmdRunner结构体处理命令的输入输出,代码如下:
use std::{io::Write, process::Command}; pub struct CmdRunner { pub command: std::process::Command, } impl CmdRunner { pub fn new(command_string: &str, input: Option<&str>) -> CmdRunner { let mut command = Command::new("script"); if let Some(input_str) = input { command .arg("-qec") .arg(command_string.replace("{}", input_str)); } else { command.arg("-qec").arg(command_string); } command.arg("/dev/null"); CmdRunner { command } } pub fn execute(&mut self, stdout: &mut impl Write) { let output = self.command.output(); match output { Ok(output) => { if output.status.success() { let stdout_str = String::from_utf8_lossy(&output.stdout); for line in stdout_str.lines() { write!(stdout, "{}\r\n", line).unwrap(); } } else { let stdout_str = String::from_utf8_lossy(&output.stdout); let stderr_str = String::from_utf8_lossy(&output.stderr); write!(stdout, "\r\n").unwrap(); write!(stdout, "Exit status: {}\r\n", output.status).unwrap(); write!(stdout, "Standard error: {}\r\n", stderr_str.trim()).unwrap(); write!(stdout, "Standard output: {}\r\n", stdout_str.trim()).unwrap(); } } Err(e) => { write!(stdout, "Error executing command: {:?}\r\n", e).unwrap(); } } } }
当执行命令python3 script.py时(脚本内容如下),'a'和'b'不会实时打印到终端,要等命令完全执行完才一起输出:
import time print('a') time.sleep(10) print('b') time.sleep(10)
需要修改代码实现像直接在终端执行那样的实时输出效果。
解决方案
核心问题分析
当前代码使用Command::output()方法,该方法会等待子进程执行完毕后,一次性返回所有stdout和stderr内容,因此无法实现实时输出。此外,Python在非终端环境下会默认缓冲stdout,即使实时读取流也可能看不到即时输出。
修改后的代码
use std::{ io::{self, BufRead, Write}, process::{Command, Stdio}, thread, }; pub struct CmdRunner { pub command: Command, } impl CmdRunner { pub fn new(command_string: &str, input: Option<&str>) -> CmdRunner { let mut command = Command::new("script"); if let Some(input_str) = input { command .arg("-qec") .arg(command_string.replace("{}", input_str)); } else { command.arg("-qec").arg(command_string); } // 配置子进程的stdout和stderr为管道,以便实时读取 command .arg("/dev/null") .stdout(Stdio::piped()) .stderr(Stdio::piped()); CmdRunner { command } } pub fn execute(&mut self, stdout: &mut impl Write) -> io::Result<()> { // 启动子进程,而不是等待其结束 let mut child = self.command.spawn()?; // 获取子进程的stdout和stderr管道 let mut child_stdout = child.stdout.take().unwrap(); let mut child_stderr = child.stderr.take().unwrap(); // 克隆输出流的引用,用于线程内写入(注意:需要保证stdout的生命周期安全) let mut stdout_clone = stdout.by_ref(); // 启动线程读取stdout,实时输出 let stdout_handle = thread::spawn(move || { let reader = io::BufReader::new(&mut child_stdout); for line in reader.lines() { if let Ok(line) = line { writeln!(stdout_clone, "{}", line).unwrap(); } } }); // 启动线程读取stderr,实时输出到系统stderr let stderr_handle = thread::spawn(move || { let reader = io::BufReader::new(&mut child_stderr); for line in reader.lines() { if let Ok(line) = line { writeln!(io::stderr(), "{}", line).unwrap(); } } }); // 等待子进程结束 let status = child.wait()?; // 等待两个读取线程结束 stdout_handle.join().unwrap(); stderr_handle.join().unwrap(); // 输出退出状态 writeln!(stdout, "\r\nExit status: {}", status)?; Ok(()) } }
额外处理:解决Python输出缓冲问题
要让Python脚本实时输出,需要在执行命令时添加-u参数,禁用stdout缓冲:
python3 -u script.py
或者在Python脚本中手动刷新输出:
import time print('a', flush=True) time.sleep(10) print('b', flush=True) time.sleep(10)
关键修改点说明
- 改用
spawn()替代output():spawn()启动子进程后立即返回Child实例,不会阻塞等待进程结束,允许我们实时读取输出流。 - 配置管道化的Stdio:通过
stdout(Stdio::piped())和stderr(Stdio::piped())将子进程的输出连接到当前进程的管道,实现流读取。 - 多线程处理输出流:用两个线程分别读取stdout和stderr,避免单个流的阻塞导致另一个流的输出延迟。
- 处理Python缓冲:Python在非终端环境下会缓冲输出,必须通过参数或代码手动禁用缓冲才能实现实时打印。
内容的提问来源于stack exchange,提问作者wyc
相关产品推荐
相关产品推荐

