Ruby Open3 Stdout 和 Stdin 如何交互


sum.rb非常简单

。您输入两个数字,它将返回总和。

# sum.rb
puts "Enter number A"
a = gets.chomp
puts "Enter number B"
b = gets.chomp
puts "sum is #{a.to_i + b.to_i}"

robot.rb使用Open3.popen3sum.rb互动。代码如下:

# robot.rb
require 'open3'
Open3.popen3('ruby sum.rb') do |stdin, stdout, stderr, wait_thr| 
  while line = stdout.gets
    if line == "Enter number An"
      stdin.write("10n")
    elsif line == "Enter number Bn"
      stdin.write("30n")
    else
      puts line
    end
  end
end

robot.rb运行失败。似乎卡在sum.rbgets.chomp

后来我发现我必须写如下才能让它工作。您需要事先以正确的顺序向它输入。

# robot_2.rb
require 'open3'
Open3.popen3('ruby sum.rb') do |stdin, stdout, stderr, wait_thr| 
  stdin.write("10n")
  stdin.write("30n")
  puts stdout.read
end

让我感到困惑的是:

  1. robot_2.rb不像与壳相互作用,它更像是喂壳需要的东西,因为我只知道。如果一个程序需要很多输入,而我们无法预测顺序怎么办?

  2. 我发现如果在sum.rb的每个puts之后添加STDOUT.flushrobot.rb可以运行。但实际上我们不能相信sum.rb的作者可以添加STDOUT.flush,对吧?

谢谢你的时间!

终于想出了怎么做。使用write_nonblockreadpartial。您必须注意的是,stdout.readpartial完全按照它所说的去做,这意味着您将不得不聚合数据并通过寻找换行符来自己执行gets

require 'open3'
env = {"FOO"=>"BAR", "BAZ"=>nil}
options = {}
Open3.popen3(env, "cat", **options) {|stdin, stdout, stderr, wait_thr|
    stdin.write_nonblock("hello")
    puts stdout.readpartial(4096)
    # the magic 4096 is just a size of memory from this example:
    # https://apidock.com/ruby/IO/readpartial

    stdin.close
    stdout.close
    stderr.close
    wait_thr.join
}

对于正在寻找更通用交互性(例如 ssh 交互(的人,您可能希望创建单独的线程来聚合 stdout 和触发 stdin。

require 'open3'
env = {"FOO"=>"BAR", "BAZ"=>nil}
options = {}
unprocessed_output = ""
Open3.popen3(env, "cat", **options) {|stdin, stdout, stderr, wait_thr|
    on_newline = ->(new_line) do
        puts "process said: #{new_line}"
        # close after a particular line
        stdin.close
        stdout.close
        stderr.close
    end
    Thread.new do
        while not stdout.closed? # FYI this check is probably close to useless/bad
            unprocessed_output += stdout.readpartial(4096)
            if unprocessed_output =~ /(.+)n/
                # extract the line
                new_line = $1
                # remove the line from unprocessed_output
                unprocessed_output.sub!(/(.+)n/,"")
                # run the on_newline
                on_newline[new_line]
            end
            # in theres no newline, this process will hang forever
            # (e.g. probably want to add a timeout)
        end
    end
    stdin.write_nonblock("hellon")
    wait_thr.join
}

顺便说一句,这不是很线程安全。这只是我发现的一个未优化但功能强大的解决方案,希望将来会得到改进。

我稍微玩了一下@jeff-hykin的回答。因此,主要的位是在非阻塞模式下从sum.rb发送数据,即使用 STDOUT.write_nonblock

# sum.rb
STDOUT.write_nonblock "Enter number An"
a = gets.chomp
STDOUT.write_nonblock "Enter number Bn"
b = gets.chomp
STDOUT.write_nonblock "sum is #{a.to_i + b.to_i}"

-- 注意STDOUT.write_nonblock调用中的n。它分隔字符串/行,这些字符串/行在robot.rbgets"get string"读取。然后robot.rb可以保持不变。我只会在条件中添加strip

# robot.rb
require 'open3'
Open3.popen3('ruby sum.rb') do |stdin, stdout, stderr, wait_thr|
  while line = stdout.gets
    puts "line: #{line}" # for debugging
    if line.strip == "Enter number A"
      stdin.write("10n")
    elsif line.strip == "Enter number B"
      stdin.write("30n")
    else
      puts line
    end
  end
end

我的 Ruby 版本是 3.0.2。

相关内容

  • 没有找到相关文章

最新更新