Ruby中Open3.popen2e执行SSH命令在Ubuntu下挂起问题
Ruby Open3.popen2e执行
ssh -N -f在Ubuntu上挂起的解决方案 问题原因
脚本在Ubuntu上挂起的核心是不同OpenSSH版本对-f参数的标准IO处理存在差异:
- macOS的OpenSSH 8.6p1执行
ssh -N -f时,fork后台进程后会立即关闭父进程的stdout/stderr流,Ruby因此能正常结束读取流程。 - Ubuntu 20.04的OpenSSH 8.2p1不会自动关闭父进程的标准流,导致Ruby的
stdout_and_stderr.each一直等待EOF触发,最终出现挂起。
解决方案
以下是几种无需依赖SSH版本的可靠处理方式:
方案1:改用Open3.capture2e(推荐)
capture2e会自动等待进程结束并读取所有输出,适配ssh -N -f父进程快速退出的场景:
require "open3" output, status = Open3.capture2e("ssh -N -f root@example.com") puts status.inspect puts output
由于ssh -N -f的父进程会立即退出(后台子进程持续运行),capture2e读取父进程的所有输出后就会结束,不会等待后台SSH进程。
方案2:显式关闭stdin并非阻塞读取输出
如果必须使用popen2e,可以关闭不需要的stdin,并通过非阻塞读取避免等待未关闭的流:
require "open3" Open3.popen2e("ssh -N -f root@example.com") do |stdin, stdout_and_stderr, wait_thread| stdin.close # SSH -N无需输入,直接关闭 puts wait_thread.value.inspect puts "closed? #{stdout_and_stderr.closed?}" # 非阻塞读取所有可用输出 stdout_and_stderr.nonblock = true begin loop do line = stdout_and_stderr.readline puts line end rescue IO::WaitReadable, EOFError # 无更多数据或流关闭,退出循环 ensure stdout_and_stderr.nonblock = false end puts wait_thread.value.inspect end
方案3:在SSH命令中重定向输出
直接让SSH把输出重定向到/dev/null,强制关闭流:
require "open3" Open3.popen2e("ssh -N -f root@example.com > /dev/null 2>&1") do |stdin, stdout_and_stderr, wait_thread| puts wait_thread.value.inspect puts "closed? #{stdout_and_stderr.closed?}" stdout_and_stderr.each do |line| puts line end puts wait_thread.value.inspect end
这种方式让SSH父进程的输出直接丢弃,Ruby读取空流后会正常结束。
注意事项
- 无需对所有命令统一加超时,针对SSH这类明确无输出的场景做特殊处理即可,不会影响其他长耗时命令。
- 如果你的SSH命令需要建立端口转发等操作,确保
-N参数符合需求(不执行远程命令)。
内容的提问来源于stack exchange,提问作者23tux
相关产品推荐
相关产品推荐

