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(())
}

功能特点

  1. 跨平台支持:可在所有主要Unix变体上工作
  2. 简单易用的API:PtyProcess::spawn创建伪终端进程
  3. 双向通信:通过get_raw_handle获取读写流
  4. 进程管理: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(())
}

注意事项

  1. 在Unix系统上,PTY功能需要相应的权限
  2. Windows上的实现可能有一些限制
  3. 正确处理子进程的退出状态很重要,避免僵尸进程
  4. 对于生产环境使用,建议添加适当的错误处理和日志记录

实际应用场景

  • 终端模拟器开发
  • 命令行工具的自动化测试
  • 交互式应用的远程控制
  • 命令行录制和回放工具

通过ptyprocess库,Rust开发者可以方便地在跨平台环境中实现伪终端功能,而无需处理不同操作系统下的底层细节。

回到顶部