跳转到内容

PTY 的启动、控制与输出获取

仅将标准输出重定向到常规管道(pipe),无法还原人类在终端中交互式执行命令的真实行为。 许多命令行工具会检测标准输出是否连接到了终端(TTY),一旦检测到管道连接,便会自动禁用彩色输出、进度条以及交互式 UI。 console2svg 为了规避这一限制,通过 PTY(Pseudo-Terminal:操作系统提供的伪终端机制)运行子进程,还原连接至真实终端的运行环境。

PTY 的创建与管理基于 Porta.Pty 库实现。 系统并不直接调用特定平台的系统调用,而是在库边界处抹平 Windows ConPTY(Pseudoconsole API)与 Unix 体系 PTY(controller/terminal 对)的底层差异,向调用上层暴露为统一的数据流。

创建子进程时,系统在设置指定宽度(列数)与高度(行数)的同时,将相同的值注入到环境变量 COLUMNS 和 LINES 中。 Shell 调用的执行在 Windows 下通过 cmd.exe /c,在 Unix 体系下则通过 /bin/sh -c。 由于 Windows 下 cmd.exe 的命令行参数解析不同于 C 运行时的标准转义规则,系统会通过 Windows 特有的安全转义逻辑组装命令行字符串。

默认情况下,从父进程继承的环境变量中,诸如 CI 或 TF_BUILD 等变量会自动从子进程环境中剔除。 这是因为现代开发工具和测试框架即便运行在 PTY 中,只要检测到这些变量存在,也会无条件降级到非交互与单色模式。 若需要完整透传父环境,可通过 --no-delete-envs 选项禁用该自动清理行为。

PTY 输出读取循环在整个生命周期内复用一个字节缓冲区、一个字符缓冲区以及一个具有状态记忆的有状态 UTF-8 解码器。 即便由于操作系统的 read 边界导致多字节字符(如全角字符)的字节序列被截断,未完成的字节状态也能可靠地传递到下一次 read 循环中继续解码。

在向终端屏幕实时转发(forwarding)输出时,系统直接将接收到的原始字节流写入标准输出。 省去中途解码为字符串再重新编码的开销,避免破坏 VT 序列(用于指定光标移动和文字颜色的转义序列)的内部结构。

作为录制数据保存的字符串会伴随流逝时间一同追加至 RecordingSession 中。 在这一阶段绝对不会按换行符进行拆分。 因为回车符(\r)、光标移动、行清除和屏幕切换等控制字符,都是后续终端仿真器精确还原屏幕状态所不可或缺的输入数据。

即便是单次屏幕重绘,子进程的输出也常常会被切分成多个极小的 PTY read 到达。 如果每次 read 都生成独立的录制事件,会导致后续的 ANSI 解析器去解析毫无意义的绘制中间状态,造成不必要的计算负荷。

因此,console2svg 通过 输出合并(Coalescing)将时间上高度邻近的多次输出整合为单一事件。 合并的时间窗口以视频单帧时长的四分之一为基准,自动调节在 2 毫秒至 20 毫秒之间。 整合后的事件以该批次最后一个数据块到达的时间戳作为记录时间。

在执行交互式捕获(interactive)期间,宿主侧终端被设置为直接接收原始按键的 Raw 模式。 这防止了方向键或 Ctrl 快捷键等操作被宿主 Shell 拦截,使其能够作为转义序列原样透传给子进程。 在 Unix 环境下,即便标准输入被重定向,系统也会在可能的情况下直接打开 /dev/tty 以维持按键输入的捕获。

从 Windows 控制台读取的文本会先按当前控制台输入代码页解码,再重新编码为 UTF-8 后转发到 PTY。VT 按键序列仍保持 ASCII,回放数据也基于规范化后的 UTF-8 输入生成。 若输入读取末尾出现被切断的不完整转义序列,剩余字节将顺延至下一次读取,防止残缺的片段被错误记录为独立的按键事件(如误判为孤立的 ESC 键)。

子进程的退出信号与 PTY 缓冲区中全部字节的读取完毕并不一定会同时发生。 即便进程刚刚终止,操作系统的内核缓冲区中仍可能残留未被处理的输出数据。

因此,在子进程退出后,输出读取器仍会持续运行最多 500 毫秒,以彻底回收尾部的残留数据。 此外,进程资源清理阶段设置了最多 1 秒的超时时间,即便底层后端的关闭逻辑因异常卡死,也能作为安全机制防止整个 CLI 进程无限期挂起。

在受限的安全策略或特殊的容器环境中,原生的 PTY 后端可能会初始化失败。 另外,也存在 PTY 进程虽已创建但因卡死而在限定时间内完全不产生输出的情况。

console2svg 在检测到启动无响应时,最多会重试 3 次 PTY 启动。 如果仍无法建立 PTY,系统将自动回退到重定向标准输入输出的普通进程执行模式。 回退模式虽然无法完整还原依赖 TTY 的富交互界面(如进度条或全屏 TUI),但能够确保非交互式命令的执行结果仍然可以被成功记录,避免任务彻底中断。