如何在Rust中实现与派生交互式进程的双向交互?
Rust实现交互式子进程调用的方案
现有代码的问题
- 语法错误:连续两次调用
spawn(),第一行已经通过Command::spawn()得到了Result<Child>类型的process,第二次对process调用spawn()不存在对应方法,编译无法通过 - 未配置标准IO句柄:默认
Command创建的子进程会继承父进程的stdio,因此你无法从child.stdout拿到读取句柄,该字段默认是None - 缺少标准输入转发逻辑:没有把当前终端的用户输入写入子进程的标准输入,自然无法进行交互
- 没有处理标准错误流:子进程输出的错误信息你无法捕获到
- 管道适配问题:交互式程序默认只有运行在终端环境下才会开启行缓冲、输出提示符,直接用管道连接的话会出现输出延迟、无提示符等问题,交互体验极差
基础管道实现(无外部依赖)
这个版本只用Rust标准库实现,仅满足基本交互需求,不会显示shell提示符:
use std::process::{Command, Stdio}; use std::io::{self, BufRead, BufReader, Write}; use std::thread; fn main() { // 配置子进程的stdio全部走管道 let mut child = match Command::new("/bin/sh") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() { Ok(c) => c, Err(e) => { eprintln!("启动子进程失败: {:?}", e); return; } }; // 拿到子进程的stdin写入句柄 let mut child_stdin = child.stdin.take().unwrap(); // 开线程转发当前进程的stdin到子进程 thread::spawn(move || { let mut stdin = io::stdin().lock(); let mut buf = [0; 1024]; loop { let n = match stdin.read(&mut buf) { Ok(0) => break, // 输入结束 Ok(n) => n, Err(_) => break, }; if child_stdin.write_all(&buf[..n]).is_err() { break; } child_stdin.flush().unwrap(); } }); // 开线程处理子进程stdout let child_stdout = child.stdout.take().unwrap(); thread::spawn(move || { let mut reader = BufReader::new(child_stdout); let mut buf = [0; 1024]; loop { let n = match reader.read(&mut buf) { Ok(0) => break, Ok(n) => n, Err(_) => break, }; io::stdout().write_all(&buf[..n]).unwrap(); io::stdout().flush().unwrap(); } }); // 开线程处理子进程stderr let child_stderr = child.stderr.take().unwrap(); thread::spawn(move || { let mut reader = BufReader::new(child_stderr); let mut buf = [0; 1024]; loop { let n = match reader.read(&mut buf) { Ok(0) => break, Ok(n) => n, Err(_) => break, }; io::stderr().write_all(&buf[..n]).unwrap(); io::stderr().flush().unwrap(); } }); // 等待子进程退出 let _ = child.wait(); }
优化PTY实现(完整交互体验)
如果需要和原生终端完全一致的交互体验(包括显示提示符、支持方向键/补全等),需要使用伪终端(PTY)模拟终端环境,可以用pty-process crate实现,在Cargo.toml添加依赖:
[dependencies] pty-process = "0.4.0"
对应实现代码:
use pty_process::Command; use std::io::{self, Read, Write}; use std::thread; fn main() { // 直接在PTY中启动子进程 let mut child = match Command::new("/bin/sh").spawn() { Ok(c) => c, Err(e) => { eprintln!("启动子进程失败: {:?}", e); return; } }; let mut pty = child.pty().try_clone().unwrap(); // 转发PTY输出到当前终端 thread::spawn(move || { let mut buf = [0; 1024]; loop { let n = match pty.read(&mut buf) { Ok(0) => break, Ok(n) => n, Err(_) => break, }; io::stdout().write_all(&buf[..n]).unwrap(); io::stdout().flush().unwrap(); } }); // 转发当前终端输入到PTY let mut stdin = io::stdin().lock(); let mut buf = [0; 1024]; loop { let n = match stdin.read(&mut buf) { Ok(0) => break, Ok(n) => n, Err(_) => break, }; if child.pty().write_all(&buf[..n]).is_err() { break; } child.pty().flush().unwrap(); } let _ = child.wait(); }
内容的提问来源于stack exchange,提问作者Reza
相关产品推荐
相关产品推荐

