用 Ruby 自动操作交互式程序:启动拥有控制终端的子进程,等待文本或正则,发送输入,处理超时、EOF 和回调,也能接管已有 IO、同时监听多个会话和转接人工交互。交互能力参考 Expect.pm,接口采用 Ruby 的属性、关键字参数和代码块。
要求 Ruby 3.2+、POSIX 系统(Linux/macOS)。运行时仅使用 Ruby 标准库,其中可独立安装的 gem 已在 gemspec 中声明,由
RubyGems/Bundler 解析。推荐入口 require "expect/pty";本项目提供独立的 Expect 类,不修改标准库的 IO#expect。
源码中的解释性注释主要使用中文;欢迎用中文或英文提交 issue 和 PR,参与方式见 贡献指南。
项目和仓库名为 expect-ruby,Gem 名为 expect-pty。在应用的 Gemfile 中添加以下内容,然后运行 bundle install:
gem "expect-pty", "~> 0.5.2", require: "expect/pty"也可直接执行 gem install expect-pty。需要跟随开发分支时,可从 GitHub 安装:
gem "expect-pty", git: "https://github.com/gatework/expect-ruby.git", branch: "main", require: "expect/pty"本地开发可改用 gem "expect-pty", path: "/path/to/expect-ruby", require: "expect/pty",也可在源码目录构建安装:
mkdir -p tmp
gem build expect-pty.gemspec --output tmp/expect-pty-0.5.2.gem
gem install ./tmp/expect-pty-0.5.2.gemrequire "expect/pty"
Expect.spawn("/bin/sh", "-i") do |shell|
shell.puts("printf 'hello ruby\\n'")
if shell.expect(/^hello ruby\r?$/, timeout: 3)
puts shell.match
else
warn shell.error
end
shell.puts("exit")
shell.soft_close(timeout: 2)
end块返回其执行结果,退出时关闭会话并回收子进程,异常和 break 也执行清理。构造或启动失败时同样释放已创建的资源;清理中的 IO
错误不会替换正在传播的原始异常。无块形式需用 ensure 显式调用 close。Expect.new 可以先创建 PTY、设置 slave.echo /
slave.winsize,然后调用实例的 spawn。
多个命令参数原样传给 Ruby exec;单个命令字符串使用 Ruby 的 shell 语义。不可信参数应使用独立参数形式。支持
env: { "NAME" => "value" } 和 chdir: "/path"。同一会话只能启动一次,启动失败抛出 Expect::SpawnError。
Expect.configure do |config|
config.timeout = 10
config.buffer_limit = 65_536
config.graceful_close = true
end
Expect.configure(debug_level: 0) # 也可用关键字修改默认值
Expect.configuration.timeout # 默认值快照,只读
Expect.spawn("/bin/sh", "-i", timeout: 3) do |session|
session.timeout = 5
session.log_stdout = true
session.raw_pty? # 布尔属性用问号方法查询
session.puts("exit")
endconfigure 校验后发布冻结的配置对象;块异常不会发布部分修改。并发调用按顺序完成读改写,不会互相覆盖不同属性。配置块在锁内执行,应保持简短,不要在块内再次调用
configure(会抛出 ThreadError)或等待其他配置线程。每个会话独立持有配置,优先使用构造参数;修改默认值不会改变已有会话,修改一个会话也不会影响其他会话。子类继承父类默认配置,可独立覆盖。
全局默认配置通常在应用启动时设置;每次会话的动态差异使用构造参数或会话属性,避免在高频路径反复获取共享配置锁。
| 属性 | 默认值 | 行为 |
|---|---|---|
timeout |
nil |
等待匹配的默认超时,秒;nil 无限,0 非阻塞轮询 |
write_timeout |
nil |
写入遇到背压时的超时,秒 |
buffer_limit |
nil |
接收缓冲最多保留的字节数;正整数或 nil(无限) |
debug_level |
0 |
1 生命周期和匹配,2 加收发内容,3 加缓冲内容 |
raw_pty |
false |
spawn 前将 slave 设为 raw,禁用回显和换行转换 |
preserve_buffer |
false |
匹配后保留完整缓冲 |
log_stdout |
false |
将接收内容输出到 $stdout |
log_listeners |
true |
将接收内容转发给 listeners |
raw_terminal |
true |
interact 期间自动设置并恢复输入终端模式,同时保留输出换行处理 |
reset_timeout_on_read |
false |
每次收到数据时重置匹配期限 |
graceful_close |
false |
close 先尝试软关闭,再完成强制清理 |
布尔属性均提供 name、name? 和 name=,按 Ruby 真值规则转换:仅 nil / false 为假,0 为真。超时必须有限且非负,nil
表示无限;无效赋值不改变原值。debug_level 仅接受整数 0..3。
session.expect("literal text", /value=(\d+)/, timeout: 5)
session.timeout = 5
session.expect("ready") # 使用会话超时
session.expect("ready", timeout: nil) # 无限等待
session.expect("ready", timeout: 0) # 匹配现有缓冲,并最多轮询读取一次
session.expect(timeout: 1) # 仅收集输出,直到超时或 EOF字符串始终按字面匹配,包括 "-i"、"-re"、"timeout" 和 "eof";正则直接使用 Ruby Regexp
。按声明顺序选择第一个能匹配的模式,不按它们在文本中的位置排序。返回模式的 1 起始序号,超时、EOF 或 IO 错误返回 nil
。超时使用单调时钟。
result = session.expect_result(/value=(\d+)/, timeout: 3)
result.matched?
result.timeout?
result.eof?
result.number
result.captures
result.session
result.error # nil、:timeout、:eof 或原始 IOError / SystemCallError 对象
number, error, match, before, after, session, captures = result.to_aResult 使用原生 Ruby Struct,支持 to_a、to_h、模式解构;没有隐式 to_ary。会话提供 last_result,以及 match、
before、after、match_number、captures、error 快捷读取方法。
成功匹配后删除匹配内容及其之前的内容,尾部留给下次匹配;超时保留缓冲,EOF 将未匹配内容放入 before 并清空缓冲。EOF
与子进程退出是不同事件,使用 wait / process_status 判断进程结果。底层 IO 错误保留原始异常,回调中的普通异常直接抛出。
接收缓冲、匹配和捕获值为 ASCII-8BIT 字节串,保留控制字符、NUL 和无效 UTF-8。固定 UTF-8
正则会等待读取末尾拆开的字符收齐后再匹配,以免尾部锚点提前命中;显示捕获内容时可 .force_encoding("UTF-8")
。任意二进制流请用字面字符串或二进制正则 /.../n。固定 UTF-8 正则遇到无效数据抛出 EncodingError;EOF
时仍未收齐的字符也属于无效编码,匹配缓冲保留原字节供诊断或二进制匹配。缓冲上限按字节截断,应为文本设置足够的上限。
正则完全遵循 Ruby:^ / $ 是行锚点,\A / \z 是整个缓冲的锚点,/m 让点号匹配换行;不再提供全局正则模式开关。
IO 等待的 timeout 不会中断单次正则计算。处理用户提供的正则或不可信长输出时,应使用有限时的正则实例,例如
Regexp.new('prompt>\\s*', timeout: 0.05);该限制同样适用于 on_sequence。正则超时原样抛出 Regexp::TimeoutError
,匹配缓冲保留,库不会修改进程全局 Regexp.timeout。timeout: 0 仍会匹配已有缓冲,不代表禁止正则计算。长输出可用日志保存全文,按业务需要设置
buffer_limit 限制匹配窗口;缩小窗口会改变 before 和跨窗口匹配范围。
session.buffer_discarded_bytes 是只读的会话累计计数,只统计 buffer_limit 裁剪掉的字节。
读取新数据、赋值 buffer 或调低上限触发裁剪时递增;成功匹配、clear_buffer、EOF 消费和转接交接均不计入。
关闭会话不会清零。日志在实际读取时保存完整接收内容,不受匹配窗口裁剪影响;它可按显式注册的秘密脱敏。
session.expect(timeout: 10) do
on(/username:\s*/i) do |connection|
connection.puts("demo")
connection.continue
end
on(/password:\s*/i) do |connection|
connection.puts(password)
connection.continue(reset_timeout: false)
end
on("ready>")
eof { |connection| warn "EOF: #{connection.before}" }
timeout { |sessions| warn "timeout: #{sessions.length} session(s)" }
end回调通过闭包访问局部变量。无参数声明块在模式构建器中执行;希望保留调用方 self 时使用 do |patterns|,调用
patterns.on(...)。所有模式注册完成后才读取 IO;注册异常或 break 不消费输入。块和位置模式不能混用。expect_result
支持同样的声明方式。
continue 继续等待并重新计时;continue(reset_timeout: false)
保留原期限,类和实例均可调用。回调返回后若保留的期限已过,不再扫描新的文本匹配,未消费的输入留给下一次等待。无回调或返回其他值时结束本次匹配。超时回调只有返回重置计时的
continue 才再次等待。EOF 回调继续时移除该源并等待其余会话;已知的 EOF 仍依次派发,全部 EOF 时直接返回,期限已过时仅对剩余活跃源触发超时。
expect / expect_result 另接受 deadline:,值为 Expect.monotonic 时钟上的绝对秒数,nil 表示不设总期限。总期限与普通
timeout 取较早者,接收重置、文本/EOF 继续及超时回调均不能延长它。同一个 deadline 可用于连续多次等待:
deadline = Expect.monotonic + 60
session.reset_timeout_on_read = true
session.expect("ready", timeout: 5, deadline: deadline)
session.expect("done", timeout: 5, deadline: deadline)上例每次等待允许最多 5 秒无新输入,两次等待共用 60 秒总预算。到达总期限后返回超时,不消费已缓冲的文本或读取新数据;已经确认的
EOF 仍可派发,全部来源结束时返回 EOF。timeout: 0
在总期限尚未到达时仍保留一次非阻塞轮询。总期限使用协作检查,不中断正在执行的回调、同步日志或单次正则;正则完成后若总期限已到,不消费其匹配结果。它不自动传给回调中的写入或其他操作。
eof / timeout 声明会占用模式序号,但事件返回的 number 为 nil。一个等待只能注册一个超时回调;它接收
所有仍在监听的会话。不需要回调时,可将 :eof / :timeout 作为位置事件参数。
Expect.expect(timeout: 5) do
on(/ready/, from: [first, second]) { |connection| puts connection.inspect }
on("done", from: third)
end
Expect.expect("ready", from: [first, second], timeout: 5)from: 指定一个或多个会话;实例块默认当前会话,类方法需提供来源。相邻且来源列表相同的模式组成一组,按组、会话、模式顺序匹配。类方法省略超时使用
Expect.configuration.timeout。
preserve_buffer = true 时,继续回调通常应自行消费匹配,例如 connection.buffer = connection.after
。如果回调没有改变缓冲,当前模式会等待缓冲变化后才重新匹配,避免反复处理同一内容。被信号中断的匹配 select/read 会自动重试,保留原期限。
Expect.open(socket) do |connection|
connection.expect("prompt>", timeout: 5)
connection.puts("command")
end
ready = Expect.readable_sessions(first, second, timeout: 5)Expect.open 支持可 select 的 File、管道、Socket 和 PTY,writer: 可指定独立写端。默认借用 IO,关闭会话不关闭原始 IO;
own: true 转移关闭责任,初始化失败也会释放接管的 IO。StringIO 可以用作日志和监听器,不能用作读取会话。
用于写入或转接的真实 IO 应在首次写入前设置 io.sync = true,并由调用方保证没有未刷新的 Ruby 写缓冲;write_nonblock
可能先阻塞刷新已有缓冲,这一步不受本库的 IO 等待期限控制。已有缓冲应在交付给本库前由调用方排空,库不会绕过缓冲或改变字节顺序。
readable_sessions 返回可读的会话对象数组,不消费数据、不包含已关闭会话、同一会话只返回一次。默认 timeout: 0;nil
无限等待。同一会话应由一个读取者驱动,多会话共同监听使用 Expect.expect。
| API | 行为 |
|---|---|
write(*objects) |
通过 to_s 转换并写入所有字节,返回字节数 |
puts(*objects) |
原生 IO 风格的换行、数组递归和 nil 返回值 |
session << object |
写入并返回会话,可链式追加 |
send_slow(*objects, delay:) |
每个字符之前等待指定秒数,同时收集返回数据 |
buffer / buffer= |
获取副本 / 复制字节并应用上限 |
clear_buffer |
清空缓冲并返回旧内容 |
stty("raw -echo") / stty |
修改终端模式 / 获取可恢复的模式字符串 |
winsize / winsize= |
读取/修改 [rows, cols],由内核通知前台进程 |
slave / tty_name / to_io / writer / fileno / tty? |
底层 IO 和终端信息 |
send_slow 在每次写入后只检查已经可读的回复,不附加固定等待;返回时不保证收齐最后一个字符引发的回复,完整对话请继续使用
expect。
大块写入遇到背压时同时读取输出,避免双向传输互相阻塞。背压等待超过 write_timeout 抛出 Expect::WriteTimeout,
error.bytes_written 给出本次 write 已被底层接受的字节数;这些字节不回滚,不要从头重发整个命令。写入、等待和背压读取中的
EINTR 均保留原期限重试。控制字符可直接发送,例如 session.write("\x03"),其信号作用取决于终端设置。send、public_send、
__send__ 保留 Ruby 反射语义。
write_timeout 是背压相关期限:持续成功的正数短写不会因为总耗时超过它而失败,也不会强行打断同步用户代码。
它与匹配的 timeout/deadline、整次 interconnect 的总 timeout 分别计算。非空写入要求底层返回实际接受的正整数字节数,
且不能超过本次片段长度;非法计数立即抛出 IOError,空写入仍返回 0。
stty 需要系统命令位于 PATH;缺失时抛出带安装提示的 IOError,原始 Errno::ENOENT 保留在 cause。窗口尺寸和人工接管的终端恢复使用
Ruby io/console。
session.log_to("session.log") # 文件追加
session.log_to("session.log", mode: "w") # 文件覆盖
session.log_to { |bytes| custom_logger.call(bytes) }
session.log_output = output_io # 借用 IO 或 callable
session.write_log("annotation\n")
session.log_output = nil # 关闭本库打开的文件,借用的 IO 保留
session.listeners = [output_io, another_session]
session.log_listeners = false日志读取用 log_output,设置用 log_output=,打开路径或注册日志块用 log_to。新建日志权限为 0600(仍受 umask
限制),已有文件保留原权限。不能同时提供日志目标与块。listeners 返回列表副本,listeners = [] 清空;替换无效目标不会丢失原目标。
所有会话默认不输出到 stdout。日志仅记录实际读取的接收字节;写入不重复记录,终端回显可能作为接收内容返回。密码交互应关闭日志、调试,并确保被控程序不回显密码。
普通 expect 按顺序同步写入日志、stdout 和 listeners,这些目标须及时消费数据;匹配的 timeout
不会中断阻塞中的输出。需要在慢目标背压时继续处理其他输入,应使用下面的 interconnect 非阻塞转接接口并设置期限。
诊断与接收字节日志分别设置。diagnostic_output: 可作为构造参数,也可通过同名属性修改;接受标准 Logger、可写目标或回调,nil
使用 stderr。目标均为借用资源,关闭会话不会关闭它们。debug_level 仍控制内容:1 为生命周期和匹配,2 增加收发字节,3
增加缓冲。Logger 的级别分别使用 info 和 debug;回调收到冻结的 Hash,包含 event、level、pid、fd、message,不包含会话对象。
require "logger" # 使用 Logger 的应用需在自己的 Gemfile 声明 logger
session.diagnostic_output = Logger.new($stderr)
session.debug_level = 2
session.redact(password, token) # 在首次通信前注册需要保护的原始字节
session.log_to("session.log")redact 复制并追加本会话的非空字符串秘密,以 [FILTERED] 遮盖 log_to / write_log
及诊断中的对应字节,支持跨读取、跨写入和重叠秘密;匹配缓冲、结果、stdout 显示和 listeners 的转发仍是原始字节。应继续关闭敏感会话的
log_stdout,并自行保护交互显示及协议转发目标。它不推断编码、终端转义、哈希或其他变换后的秘密,也不能删除已输出的日志。
过滤器最多延迟最长秘密长度减一的尾部字节,EOF、目标替换和显式关闭时交付剩余内容;流边界处疑似秘密前缀也会被遮盖。发送与接收诊断各自保留过滤状态;启用脱敏时,level
3 的缓冲快照只显示 [FILTERED],避免部分消费或裁剪后剩下的秘密片段绕过过滤。日志回调的分块边界因此可能变化。GC
兜底不会调用用户日志回调,需显式关闭会话以交付过滤器尾部。
应用需要过滤自己的日志或错误文本时,可以直接使用独立的字节过滤器,无需打开 PTY 或创建会话:
require "expect/redactor"
safe_message = Expect::Redactor.redact(message, [password], replacement: "[REDACTED]")
filter = Expect::Redactor.new([password], replacement: "[REDACTED]")
output.write(filter.append(chunk)) # 每个输出流使用独立实例
output.write(filter.finish)patterns= 用由非空字符串组成的数组替换后续规则,空数组表示不注册秘密;输入数组、字符串和替换标记都会复制,
无效更新不改变已有规则或暂存数据。已输出的内容不能撤回,更新后也会保留先前标记为隐藏的尾部区间。
redact 处理完整文本,只匹配完整秘密;finish 默认还隐藏未完成的秘密前缀。已确定输入完整的调用方可用
finish(partial: false)。连续或重叠的隐藏区间合并成一个替换标记。过滤不自动识别终端控制符或编码,过滤器本身不持有 IO,
也不负责会话作用域或异常对象的安全字段选择。该公共接口从 0.5.0 开始提供。
session.interact(input: $stdin, escape: "\x1d", output: $stdout) # Ctrl-]
Expect.open($stdin) do |input|
input.listeners = [session]
session.listeners = [$stdout]
input.on_sequence("\x1d") { false }
Expect.interconnect(input, session, timeout: 60)
endon_sequence(sequence) { ... } 注册字符串、原生正则或 :eof,通过闭包传递参数。无回调、返回 nil / false 停止,其他 Ruby
真值(包括 0)继续;字符串 "EOF" 按字面匹配。转接返回导致停止的会话,超时或所有 EOF 回调均继续时返回 nil。
interconnect 统一调度真实 IO 的非阻塞读写;慢目标不会阻止其他源前进,等待同时受总 timeout 和目标会话的 write_timeout
约束。总期限到达返回 nil,目标写期限先到则抛出 WriteTimeout。超时后的字面转义前缀只尝试非阻塞发送,不再等待下游。作为写入目标但未显式列出的
Expect 会话,背压期间读取的回复保留在其匹配缓冲;需要同时转发这些回复时,把它也传给 interconnect。
每个源独立保存待发送数据以及各目标的发送位置,source.pending_output? 表示仍有未交付内容。超时或异常后再次对同一源调用
interconnect,会接着发送未完成的后缀,已完成的目标不会重复接收;转义回调在前缀交付后执行。待发送数据与 buffer
中尚未处理的输入分开保存,修改 listeners 仅影响后续数据,旧数据仍发往原目标。恢复时不要把原始数据再次赋给 buffer
,也不要在排空旧输出前插入新的直接写入;关闭源会话会放弃其待发送数据。转接保留的输入暂不按 buffer_limit 裁剪,下次匹配时重新应用该上限。
自定义写入对象必须及时返回实际接受的字节数,短写入会继续发送后缀,零、负数或非法返回值抛出 IOError
。对象若先写入再抛错而不报告进度,库无法推断其副作用。日志、用户回调及自定义 write / flush 同步运行,应由调用方保证它们不会无限阻塞;上述
IO 期限不会强行中断这些代码。普通 expect 的同步日志和监听器输出也不受匹配等待期限限制。
字面转义可以跨读取完整过滤,尾部留给下次调用。正则转义使用历史记录,默认最多保留最近 65,536 字节;设置 buffer_limit
后改用该值。正则及其锚点作用于当前历史窗口,超过窗口的跨读取正则无法匹配,已实时转发的前缀也无法撤回;零长度正则匹配抛出
ArgumentError。日志包括被转接过滤的转义,显式启用 redact 时遮盖注册秘密;在 expect / interconnect 之间切换不会重复记录。
一次转接尚未返回时,递归 interconnect 的来源若与活跃来源重叠,会在移动缓冲和修改发送游标前抛出
Expect::ReentrancyError。完全独立的来源仍可嵌套转接;on_sequence 中的嵌套 expect/expect_result 及返回后再次转接仍受支持。
自定义 write 若已产生副作用却抛错、未返回计数,库无法推断已接受的字节数,此时不能保证恢复交付恰好一次。
这一保护不代表所有会话 API 都可以跨线程并发调用。
interact 会自动设置并恢复本地输入终端模式,同时保留输出换行处理;输入会话的 raw_terminal = false 将设置交给调用方。通用的
interconnect 只负责字节转发,由调用方管理终端模式。interact 还会恢复临时监听组、日志开关和转义设置,包括超时和异常路径。
对同一连接重复传入同一个原始输入 IO 时,interact 会复用输入包装器,接续上次预读的尾部。包装器由该连接持有,关闭连接时释放,但不关闭借用的原始
IO;已关闭的输入或包装器不再复用。需要跨连接共享或自行管理输入生命周期时,显式传入 Expect.open(input) 创建的会话。
status = session.soft_close(timeout: 3, term_timeout: 1)
status ||= session.hard_close(timeout: 0.2)
session.close(graceful: true) # 先软关闭,必要时继续硬关闭soft_close:等待自然 EOF 并收集尾部输出,然后关闭所属 IO、等待进程退出;超时后最多发送 TERM, 不发送 KILL。timeout:是自然退出阶段的期限(默认 15 秒),term_timeout:是发 TERM 后的等待时间(默认 1 秒)。未退出返回nil,保留 PID,可继续wait或hard_close。hard_close:立即关闭所属 IO,不收集尾部输出;等待timeout:,必要时发送 TERM 再等待同样时长,仍未退出则 KILL 并最多等待 1 秒。默认timeout: 0.2,必须有限。- 两者返回已回收的
Process::Status,没有子进程或尚未回收时返回nil;重复调用保留已获得的状态。借用 IO 不关闭。 close(graceful: graceful_close?):可选先软关闭,ensure中硬关闭,返回nil。块生命周期使用它完成清理;软关闭发生日志异常时也会回收子进程。wait(timeout: nil):等待并回收,返回Process::Status;超时返回nil。process_status非阻塞查询,exit_code读取普通退出码,信号退出看process_status.termsig。closed?表示会话 IO 已关闭;alive?/pid表示子进程状态。软关闭后可能同时closed? == true、alive? == true。成功回收后 PID 为nil。
关闭只负责会话直接启动的子进程;垃圾回收提供非阻塞的强制清理兜底,不执行软关闭等待。优先使用块或 ensure 管理资源。
- 将不可信命令和参数分别传给
spawn,例如Expect.spawn("ssh", host);单个命令字符串会使用 Ruby 的 shell 语义。stty参数经拆分后作为独立参数传给进程,不拼接 shell 命令。 - 会话日志可能记录密码回显、令牌和其他敏感字节。新日志文件以
0600创建,已有文件保留原权限;请按需关闭log_to、log_stdout和调试输出,并管理日志留存。 spawn在子进程中使用fork后的 Ruby 操作与exec。高度多线程的宿主进程,尤其使用第三方 C 扩展时,可能受到 fork 时其他线程持锁的影响;尽量在启动其他线程前创建会话,并在自己的运行环境中验证。- 不可信正则可能耗费较长时间;使用带
timeout:的Regexp实例,并为匹配缓冲设置合适的buffer_limit。普通expect的 IO 期限不打断单次正则或同步回调。
bundle install
bundle exec rake # RuboCop + 完整测试
script/ci # 与 CI 相同:检查、测试、构建和隔离安装验证
ruby examples/dialogue.rb
ruby examples/kibitz/test_kibitz.rb
ruby examples/ssh_auto.rb # 登录后执行命令,再交给人工输入
ruby examples/ssh_auto.rb --no-interact
ruby examples/ssh_interact.rb --auto普通测试使用真实 PTY、管道和 socket,无需 SSH 服务或账户。Kibitz 的双终端示例与验证见 examples/kibitz/。
GitHub Actions 在推送 main、推送 v* 标签、提交到
main 的 Pull Request 或手动触发时运行。流水线覆盖 Ubuntu 24.04 / macOS 15 与 Ruby 3.2、3.3、3.4、4.0 的 8 种组合;每个环境执行
script/ci,包括真实 PTY 测试和构建包的隔离安装验证。Ubuntu / Ruby 4.0 作业保留已验证的 Gem 构建产物 14 天,可从该次工作流的
Artifacts 下载。
运行前先执行 bundle install。Gem 库的开发锁文件 Gemfile.lock 保留在本地,各 Ruby 环境按 Gemfile 解析兼容依赖。生成文件写入已忽略的
tmp/,Gem 构建产物位于 tmp/ci/,发布候选包位于 tmp/release/。安装验证会清除外部 Bundler 环境,分别运行普通 RubyGems
加载和只声明 expect-pty 的 Bundler 应用,检查运行时依赖、终端模式、窗口大小及真实 PTY 对话。更新工作流中的 Action
时,应同步更新固定的提交 SHA 和版本注释。
运行时依赖只在 expect-pty.gemspec 声明,开发依赖放在 Gemfile 的 development / test 组;安装或使用本库不会引入
Minitest、Rake、RuboCop 及发布工具的依赖。
| 运行时模块 | Gem | 用途 |
|---|---|---|
forwardable |
forwardable |
会话配置委托 |
io/console |
io-console |
终端模式和窗口大小 |
io/wait |
io-wait |
IO 可读等待 |
shellwords |
shellwords |
stty 参数拆分 |
stringio |
stringio |
Ruby puts 语义 |
pty |
Ruby 自带扩展 | POSIX 伪终端,无独立 gem |
仅发布 RubyGems 使用 ruby script/release.rb --rubygems-only,直接复用本机已有的 Gem 登录状态;添加 --dry-run
可先完成本地检查、测试、构建和安装验证。需要同时创建 GitHub Release 时使用 ruby script/release.rb,也可以在 GitHub Actions
手动运行 Release 工作流。版本准备、Actions 凭据和失败重试见 发布说明。
SSH 示例用 SSH_USER、SSH_HOST、SSH_KNOWN_HOSTS 配置,密码隐藏输入或从 EXPECT_PASSWORD 读取;非本地主机要求受信任的
known_hosts 文件。ssh_auto.rb 顶部 COMMANDS 可直接修改,日志写入 tmp/ssh-auto/,权限 0600。
多脚本验证入口为 test/integration/ssh_scripts.rb,人工/自动接管入口为 examples/ssh_interact.rb
,详细配置及日志检查见 SSH 测试说明。
当前接口迁移表见 接口说明,本次与历史验证分列在 验证记录
。此次重构直接移除了旧入口,不提供兼容别名。
防火墙连接器已迁移到相邻的 algosec 项目;本库只保留 Expect 与 expect-pty 通用传输能力。