ruby open3 stdout和stdin如何交互

pu82cl6c  于 2023-04-05  发布在  Ruby
关注(0)|答案(2)|浏览(162)

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.rbgets.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需要的东西,因为我只是知道。如果程序需要很多输入,而我们无法预测顺序,该怎么办?
    1.我发现如果在sum.rb中的每个puts之后添加STDOUT.flushrobot.rb可以运行。但实际上我们不能相信sum.rb的作者可以添加STDOUT.flush,对吗?
    谢谢你的时间!
pprl5pva

pprl5pva1#

最后找到了如何做到这一点。使用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
}

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

1sbrub3j

1sbrub3j2#

我尝试了一下@jeff-hykin的答案。所以,主要的一点是以非阻塞模式发送来自sum.rb的数据,即使用STDOUT.write_nonblock

# sum.rb
STDOUT.write_nonblock "Enter number A\n"
a = gets.chomp
STDOUT.write_nonblock "Enter number B\n"
b = gets.chomp
STDOUT.write_nonblock "sum is #{a.to_i + b.to_i}"

--注意STDOUT.write_nonblock调用中的\n。它分隔字符串/行,这些字符串/行是用robot.rb中的gets“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("10\n")
    elsif line.strip == "Enter number B"
      stdin.write("30\n")
    else
      puts line
    end
  end
end

Ruby版本是3.0.2。

相关问题