You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 01:03:11