Ruby中使用Open3调用dd命令复制树莓派镜像的进度监控问题
解决Ruby调用dd复制镜像时的进度监控问题
我明白你遇到的困扰了——手动按Ctrl+T能让dd正常输出进度,但用Open3.popen2e调用时,不管是发信号还是写stdin都没效果。这俩问题其实都有明确的根源,咱们一步步解决:
为什么你的现有方法无效?
- Process.kill报错EPERM:你当前是给
sudo进程发信号(wait_thr.pid是sudo的PID),但实际执行复制的是sudo启动的dd子进程。而且普通用户给sudo进程发信号会被权限拦截,就算信号能传递到sudo,它也不会转发给dd。 - 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
相关产品推荐
相关产品推荐

