# virtualShell **Repository Path**: yao_mi/virtual-shell ## Basic Information - **Project Name**: virtualShell - **Description**: 一个基于伪终端 (forkpty) 的轻量级 C++ 库,用于启动交互式 Bash 子进程,非阻塞地执行命令,并精确捕获子进程的标准输出。通过自定义的标记检测机制,自动去除命令回显与提示符,仅返回纯命令输出结果。 - **Primary Language**: C++ - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-25 - **Last Updated**: 2026-04-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 以下是为您提供的仓库介绍 (`README.md`) 和开源说明,您可以直接放到 Git 仓库中使用。 --- # virtualShell – 伪终端交互式 Bash 控制器 (C++) 一个基于伪终端 (`forkpty`) 的轻量级 C++ 库,用于启动交互式 Bash 子进程,非阻塞地执行命令,并精确捕获子进程的标准输出。通过自定义的标记检测机制,自动去除命令回显与提示符,仅返回纯命令输出结果。 ## ✨ 特性 - **伪终端会话**:通过 `forkpty` 创建完整伪终端,启动真实的交互式 Bash,保留 ANSI 颜色与环境变量。 - **非阻塞执行**:使用 `select` + 非阻塞 I/O,不会阻塞主线程;支持 `sendCommand()` + `pollCommand()` 异步工作流程。 - **智能输出裁剪**:基于 `BracketMatcher` 状态机检测命令结束标记,自动移除命令本身的回显行和前置提示符,返回纯净的命令输出。 - **超时控制**:同步 `execute()` 提供超时参数,避免子进程永久挂起。 - **多实例并行**:每个对象独立 Bash 子进程,支持同时操控多个独立 Shell 环境(例如自动激活不同的 conda 环境)。 - **空缓冲处理**:`drainAll()` 方法可清空残留的历史输出,确保下次命令执行时不混入旧数据。 - **零外部依赖**(除标准 POSIX 库和 `libutil`,用于 `forkpty`)。 ## 📦 适用场景 - 需要精确获取 shell 命令执行结果的自动化工具或 bot。 - 需要长时间保持环境状态(如 conda activate、环境变量设置)的脚本托管。 - 依赖于交互式会话的 CLI 包装器(例如自动安装脚本、tutorial runner)。 ## 📁 文件说明 仓库包含一个单头文件:`virtualShell.hpp`,内置完整的实现。您可以直接将其包含到项目中。 ## 🛠️ 编译与使用 ### 编译要求 - 支持 C++11 或以上版本的编译器。 - Linux / Unix 类系统(需要 `` 以及 `forkpty` 函数)。 - 链接 `libutil`:通常只需在编译时添加 `-lutil`。 ### 示例编译命令 ```bash # 编译测试程序(文件内自带的 TEST_SELF 宏) # 将 .hpp 重命名为 .cpp 后编译: mv virtualShell.hpp virtualShell.cpp g++ -std=c++11 -DTEST_SELF virtualShell.cpp -o virtualShell -lutil # mv virtualShell.cpp virtualShell.hpp ``` ### 最小示例 ```cpp #include #include "virtualShell.hpp" int main() { // 初始化 Shell,并在其中执行一条环境初始化命令 virtualShell shell("source ~/miniconda3/etc/profile.d/conda.sh && conda activate base", "myconda0"); // 同步执行命令(默认阻塞 15 秒超时) shell.execute("python --version"); std::string result = shell.get_cmd_out(); // 获取并裁剪后的输出 std::cout << result; // 或使用“发送-轮询”非阻塞方式 shell.sendCommand("echo 'Hello from virtual shell'"); while (!shell.pollCommand()) { usleep(10000); // 模拟做其他事情 } std::string result = shell.get_cmd_out(); // 获取并裁剪后的输出 std::cout << result; return 0; } ``` ## 🔬 工作原理 1. `virtualShell` 构造时通过 `forkpty()` 创建一个子进程并启动 `bash --norc --noprofile -i`。 2. 初始化阶段发送 `echo [__INIT_END__]` 标记,状态机检测到两个标记后(跳过命令自身回显),确认 Shell 就绪,并自动提取初始化后的完整输出。 3. 每次 `execute()` 或 `sendCommand()` 都会生成独特的结束标记,如 `echo [__CMD_END__1__]`,状态机会在输出流中识别该标记的第一次出现(命令回显)和第二次出现(实际 echo 输出),从而准确定位命令输出的开始与结束位置。 4. `get_cmd_out()` 根据标记位置裁剪掉命令回显行和多余的提示符,仅保留纯命令输出。 ## ⚠️ 注意事项 - 由于使用真实的 Bash 交互进程,**请勿在短时间内发送大量命令**,以免缓冲区溢出或子进程处理不及。需要时可适当在命令间添加 `sleep`。 - `drainAll()` 使用试探性读取来清空残余数据,如果 Shell 仍在持续输出长文本,可能无法一次清空干净,可连续调用多次或适当增加等待时间。 - 初始化阶段的命令(`initCmd`)的回显处理与普通命令略有不同,已通过状态机处理,但仍需保证该命令本身不会产生大量额外输出干扰标记检测。 - 该类**非线程安全**,同一 `virtualShell` 对象的操作应在同一线程内串行化,或者外部加锁。 ## 📄 开源许可 本项目采用 **MIT 许可证**,允许自由使用、修改和分发。 *(请将以下许可证文本放入 `LICENSE` 文件)* ``` MIT License Copyright (c) [年份] [您的姓名/组织] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: ... ``` ## 🤝 贡献 欢迎提交 Issue 或 Pull Request,共同改进这个项目。