ruby open3 stdout和stdin如何互动

时间:2017-03-26 07:56:27

标签: ruby popen3

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 A\n"
      stdin.write("10\n")
    elsif line == "Enter number B\n"
      stdin.write("30\n")
    else
      puts line
    end
  end
end

robot.rb未能投放。似乎它被sum.rb gets.chomp困住了。

后来我发现我必须按照以下方式编写才能使其正常工作。你需要事先用正确的顺序输入它。

# robot_2.rb
require 'open3'

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

困惑我的是:

  1. robot_2.rb不像与shell 交互,它更像提供shell所需的内容,因为我只知道。如果一个程序需要很多输入而我们无法预测订单怎么办?

  2. 我发现在STDOUT.flush中的puts sum.rb之后添加了robot.rbsum.rb可以运行。但实际上我们不能相信STDOUT.flush的作者可以添加public void updateUser(string name, string passwd) ,对吧?

  3. 谢谢你的时间!

1 个答案:

答案 0 :(得分:0)

最后弄清楚了该怎么做。使用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("hello\n")

    wait_thr.join
}

顺便说一句,这不是非常线程安全的。我发现这只是一个未优化但实用的解决方案,有望在将来得到改进。

相关问题