Rust伪终端处理库ptyprocess的使用:ptyprocess实现跨平台伪终端创建与子进程交互
Rust伪终端处理库ptyprocess的使用:ptyprocess实现跨平台伪终端创建与子进程交互
ptyprocess是一个提供Unix PTY/TTY接口的Rust库,旨在支持所有主要的Unix变体。该库最初是为expectrl项目开发的,如果你需要更高层次的操作,可以查看zhiburt/expectrl项目。
基本使用示例
use ptyprocess::PtyProcess;
use std::io::{BufRead, BufReader, Result, Write};
use std::process::Command;
fn main() -> Result<()> {
// 启动一个cat进程
let mut process = PtyProcess::spawn(Command::new("cat"))?;
// 创建通信流
let mut stream = process.get_raw_handle()?;
// 向进程发送消息
writeln!(stream, "Hello cat")?;
// 从流中读取一行
let mut reader = BufReader::new(stream);
let mut buf = String::new();
reader.read_line(&mut buf)?;
println!("line was entered {buf:?}");
// 停止进程
assert!(process.exit(true)?);
Ok(())
}
完整示例代码
下面是一个更完整的示例,展示了如何使用ptyprocess创建伪终端并与子进程交互:
use ptyprocess::PtyProcess;
use std::io::{BufRead, BufReader, Result, Write};
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;
fn main() -> Result<()> {
// 配置要启动的命令
let mut cmd = Command::new("bash");
cmd.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
// 创建伪终端进程
let mut process = PtyProcess::spawn(cmd)?;
// 获取原始句柄进行读写
let mut stream = process.get_raw_handle()?;
// 创建一个线程来读取子进程输出
let reader_thread = {
let mut stream = stream.try_clone()?;
thread::spawn(move || {
let mut reader = BufReader::new(stream);
let mut line = String::new();
loop {
line.clear();
match reader.read_line(&mut line) {
Ok(0) => break, // EOF
Ok(_) => {
print!("子进程输出: {}", line);
}
Err(e) => {
eprintln!("读取错误: {}", e);
break;
}
}
}
})
};
// 向子进程发送命令
writeln!(stream, "echo 'Hello from bash'")?;
writeln!(stream, "ls -l")?;
writeln!(stream, "exit")?;
// 等待子进程结束
thread::sleep(Duration::from_secs(1));
// 确保进程已退出
if !process.exit(true)? {
eprintln!("子进程未正常退出");
}
// 等待读取线程结束
reader_thread.join().unwrap();
Ok(())
}
功能特点
- 跨平台支持:可在所有主要Unix变体上工作
- 简单易用的API:
PtyProcess::spawn
创建伪终端进程 - 双向通信:通过
get_raw_handle
获取读写流 - 进程管理:
exit
方法控制进程终止
安装
在Cargo.toml中添加依赖:
[dependencies]
ptyprocess = "0.4.1"
或者使用cargo命令安装:
cargo add ptyprocess
该库采用MIT许可证,适用于命令行接口和开发工具类项目。
1 回复
Rust伪终端处理库ptyprocess的使用指南
概述
ptyprocess
是一个Rust库,用于跨平台创建伪终端(PTY)并与子进程进行交互。它简化了在Unix-like系统和Windows上创建和控制伪终端的过程,使得开发者可以方便地实现终端模拟器、命令行工具测试等需要PTY功能的场景。
主要特性
- 跨平台支持(Unix-like系统和Windows)
- 简单的API创建伪终端
- 支持与子进程的交互
- 可调整终端大小
- 非阻塞IO支持
安装
在Cargo.toml中添加依赖:
[dependencies]
ptyprocess = "0.10"
基本使用方法
创建伪终端并启动子进程
use ptyprocess::PtyProcess;
use std::process::Command;
fn main() -> std::io::Result<()> {
// 创建一个命令
let cmd = Command::new("/bin/bash");
// 创建伪终端并启动子进程
let mut pty = PtyProcess::spawn(cmd)?;
// 获取PTY的主从设备文件描述符
let master = pty.master()?;
// 现在可以通过master与子进程交互
// ...
Ok(())
}
读写操作示例
use ptyprocess::PtyProcess;
use std::process::Command;
use std::io::{Write, Read};
fn main() -> std::io::Result<()> {
let cmd = Command::new("/bin/bash");
let mut pty = PtyProcess::spawn(cmd)?;
let mut master = pty.master()?;
// 向子进程写入数据
master.write_all(b"ls -la\n")?;
// 从子进程读取输出
let mut buf = [0u8; 1024];
let n = master.read(&mut buf)?;
println!("Received: {}", String::from_utf8_lossy(&buf[..n]));
Ok(())
}
调整终端大小
use ptyprocess::{PtyProcess, Winsize};
use std::process::Command;
fn main() -> std::io::Result<()> {
let cmd = Command::new("/bin/bash");
let mut pty = PtyProcess::spawn(cmd)?;
// 设置终端大小为80列x24行
let winsize = Winsize {
ws_row: 24,
ws_col: 80,
ws_xpixel: 0,
ws_ypixel: 0,
};
pty.set_winsize(winsize)?;
Ok(())
}
高级用法
非阻塞IO
use ptyprocess::PtyProcess;
use std::process::Command;
use std::os::unix::io::AsRawFd;
use nix::fcntl::{OFlag, fcntl, FcntlArg};
fn set_nonblocking(pty: &mut PtyProcess) -> std::io::Result<()> {
let fd = pty.master()?.as_raw_fd();
let flags = fcntl(fd, FcntlArg::F_GETFL)?;
fcntl(fd, FcntlArg::F_SETFL(OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK))?;
Ok(())
}
fn main() -> std::io::Result<()> {
let cmd = Command::new("/bin/bash");
let mut pty = PtyProcess::spawn(cmd)?;
set_nonblocking(&mut pty)?;
// 现在可以非阻塞地读写
// ...
Ok(())
}
处理信号和进程状态
use ptyprocess::PtyProcess;
use std::process::Command;
use nix::sys::wait::WaitStatus;
fn main() -> std::io::Result<()> {
let cmd = Command::new("/bin/bash");
let mut pty = PtyProcess::spawn(cmd)?;
// 检查进程状态
match pty.wait(false)? {
WaitStatus::Exited(pid, status) => {
println!("Process {} exited with status {}", pid, status);
}
WaitStatus::Signaled(pid, signal, _) => {
println!("Process {} killed by signal {}", pid, signal);
}
_ => {
println!("Process is still running");
}
}
Ok(())
}
完整示例
下面是一个完整的ptyprocess使用示例,演示了如何创建伪终端、与子进程交互并处理输出:
use ptyprocess::{PtyProcess, Winsize};
use std::process::Command;
use std::io::{Write, Read};
use std::thread;
use std::time::Duration;
fn main() -> std::io::Result<()> {
// 创建bash命令
let cmd = Command::new("/bin/bash");
// 生成伪终端并启动子进程
let mut pty = PtyProcess::spawn(cmd)?;
// 设置终端大小
let winsize = Winsize {
ws_row: 24,
ws_col: 80,
ws_xpixel: 0,
ws_ypixel: 0,
};
pty.set_winsize(winsize)?;
// 获取主设备
let mut master = pty.master()?;
// 向bash发送命令
master.write_all(b"echo 'Hello from PTY!'\n")?;
master.write_all(b"ls -la\n")?;
// 读取输出
let mut buf = [0u8; 1024];
loop {
match master.read(&mut buf) {
Ok(n) if n > 0 => {
println!("Output: {}", String::from_utf8_lossy(&buf[..n]));
}
Ok(_) => break,
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
// 非阻塞模式下无数据可读
thread::sleep(Duration::from_millis(100));
continue;
}
Err(e) => return Err(e),
}
}
// 检查进程状态
match pty.wait(false)? {
WaitStatus::Exited(pid, status) => {
println!("Process {} exited with status {}", pid, status);
}
WaitStatus::Signaled(pid, signal, _) => {
println!("Process {} killed by signal {}", pid, signal);
}
_ => {
println!("Process is still running");
}
}
Ok(())
}
注意事项
- 在Unix系统上,PTY功能需要相应的权限
- Windows上的实现可能有一些限制
- 正确处理子进程的退出状态很重要,避免僵尸进程
- 对于生产环境使用,建议添加适当的错误处理和日志记录
实际应用场景
- 终端模拟器开发
- 命令行工具的自动化测试
- 交互式应用的远程控制
- 命令行录制和回放工具
通过ptyprocess
库,Rust开发者可以方便地在跨平台环境中实现伪终端功能,而无需处理不同操作系统下的底层细节。