使用Open3.popen3时如何在每次输入后读取stdout返回数据
解决方案
你遇到的缓冲问题90%以上不是Ruby层面导致的,而是你调用的cmd子进程自身的标准输出缓冲机制导致的:绝大多数程序在非交互终端模式下会默认开启块缓冲,只有缓冲写满或者进程退出时才会一次性刷出所有输出内容,所以你才会在所有输入完成后才能拿到完整输出。
以下是可直接落地的解决方法:
方法1:用伪终端(PTY)模式运行子进程(最通用方案)
用Ruby内置的PTY模块替代Open3.popen3,伪终端会让子进程认为自己运行在交互终端环境下,默认开启行缓冲,每输出一行就会立即刷出,完美匹配你的实时读写需求。
require 'pty' PTY.spawn(cmd) do |stdout, stdin, pid| # 关闭Ruby层面的IO缓冲 stdout.sync = true stdin.sync = true # 第一次输入+实时读取输出更新参数 stdin.puts "e #{$initialState}" current_output = stdout.gets updateParameters(current_output) if current_output # 第二次输入(用第一次输出更新后的参数)+读取输出 stdin.puts "e #{$nextState}" current_output = stdout.gets updateParameters(current_output) if current_output # 第三次输入+读取输出 stdin.puts "e #{$nextState}" current_output = stdout.gets updateParameters(current_output) if current_output stdin.close # 等待子进程退出 Process.wait(pid) end
方法2:无法使用PTY时的备选方案
如果你的运行环境不支持PTY(比如部分精简容器环境),可以强制子进程关闭输出缓冲:
- 如果
cmd是你自己开发的程序:直接修改源码开启行缓冲即可,比如C程序添加setvbuf(stdout, NULL, _IOLBF, 0),Ruby编写的cmd添加$stdout.sync = true - 如果是第三方不可修改的程序:用
stdbuf工具包装调用命令,把你原来的cmd替换为stdbuf -oL #{cmd},即可强制子进程标准输出按行刷出,配合修改后的Open3同步读写逻辑也可以实现需求:
Open3.popen3("stdbuf -oL #{cmd}") do |stdin, stdout, stderr, wait_thread| stdin.sync = true stdout.sync = true # 写一次读一次的同步逻辑,替换原来的连续写入+异步线程读逻辑 stdin.puts "e #{$initialState}" updateParameters(stdout.gets) stdin.puts "e #{$nextState}" updateParameters(stdout.gets) stdin.puts "e #{$nextState}" updateParameters(stdout.gets) stdin.close wait_thread.value end
注意事项
- 如果你的子进程输出不是按行返回的,可将
stdout.gets替换为stdout.readpartial(1024)按字节数读取,避免阻塞 - 不要将读写逻辑拆分到不同线程异步执行,非常容易出现输入输出时序不匹配的问题
内容的提问来源于stack exchange,提问作者anjali ahlawat
相关产品推荐
相关产品推荐

