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

Ruby中使用Open3调用dd命令复制树莓派镜像的进度监控问题

解决Ruby调用dd复制镜像时的进度监控问题

我明白你遇到的困扰了——手动按Ctrl+T能让dd正常输出进度,但用Open3.popen2e调用时,不管是发信号还是写stdin都没效果。这俩问题其实都有明确的根源,咱们一步步解决:

为什么你的现有方法无效?

  1. Process.kill报错EPERM:你当前是给sudo进程发信号(wait_thr.pid是sudo的PID),但实际执行复制的是sudo启动的dd子进程。而且普通用户给sudo进程发信号会被权限拦截,就算信号能传递到sudo,它也不会转发给dd。
  2. stdin发送20.chr无效:Ctrl+T不是通过标准输入传递的——终端会把这个快捷键转换成SIGINFO(macOS)或SIGUSR1(Linux)信号直接发给dd进程。当dd在管道环境中运行(而非终端)时,它根本不会监听stdin的这个字符。

解决方案

方案一:找到dd进程并发送正确信号(原生dd方案)

核心思路是:定位sudo启动的dd子进程PID,用sudo权限给它发送进度信号。

rpath = device_path.gsub(/disk/, "rdisk")
puts "\n\nCopying image to #{rpath}"

cmd = "sudo dd bs=1m if=#{source_path} of=#{rpath}"
Open3.popen2e(cmd) do |stdin, stdout_err, wait_thr|
  sudo_pid = wait_thr.pid

  # 实时输出dd的日志
  Thread.new do
    stdout_err.each { |line| puts line }
  end

  # 定期给dd发进度信号
  Thread.new do
    loop do
      sleep 5
      # 通过sudo的PID找到它的子进程dd的PID
      dd_pid = `pgrep -P #{sudo_pid}`.strip
      next if dd_pid.empty?

      # 根据系统选择信号:macOS用INFO,Linux用USR1
      signal = RUBY_PLATFORM.include?('darwin') ? 'INFO' : 'USR1'
      # 必须用sudo kill,因为dd是root权限运行的
      system("sudo kill -#{signal} #{dd_pid}")
    rescue => e
      puts "发送信号时出错:#{e.message}"
      break
    end
  end

  wait_thr.value
end

方案二:用pv工具简化监控(更推荐)

如果你的系统能安装pv(macOS用brew install pv,Linux用apt install pv),这是最省心的方式——pv会自动显示进度、传输速率、剩余时间等信息,不需要手动发信号:

rpath = device_path.gsub(/disk/, "rdisk")
puts "\n\nCopying image to #{rpath}"

# pv输出实时进度,管道给dd写入
cmd = "sudo pv -pterb #{source_path} | sudo dd bs=1m of=#{rpath}"
Open3.popen2e(cmd) do |stdin, stdout_err, wait_thr|
  stdout_err.each { |line| puts line }
  wait_thr.value
end

-pterb参数分别对应:显示进度条、传输速率、已用时间、剩余时间、总字节数。

方案三:用伪终端模拟手动终端场景

如果一定要模拟手动按Ctrl+T的效果,可以用Ruby的PTY模块创建伪终端,让dd认为自己在终端环境中运行:

require 'pty'
rpath = device_path.gsub(/disk/, "rdisk")
puts "\n\nCopying image to #{rpath}"

cmd = "sudo dd bs=1m if=#{source_path} of=#{rpath}"
PTY.spawn(cmd) do |stdout, stdin, pid|
  # 实时输出日志
  Thread.new do
    stdout.each { |line| puts line }
  rescue Errno::EIO
    # 进程结束时可能触发这个错误,忽略即可
  end

  # 定期发送Ctrl+T字符
  Thread.new do
    loop do
      sleep 5
      stdin.write(20.chr)
      stdin.flush
    rescue => e
      puts "发送输入时出错:#{e.message}"
      break
    end
  end

  Process.wait(pid)
end

注意:如果你的sudo需要输入密码,这个方案可能需要提前配置免密sudo,否则会卡住等待输入。

内容的提问来源于stack exchange,提问作者rustyshamrock

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 13:17:42