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.popen3
与sum.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.rb
的gets.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
让我感到困惑的是:
robot_2.rb
不像与壳相互作用,它更像是喂壳需要的东西,因为我只知道。如果一个程序需要很多输入,而我们无法预测顺序怎么办?我发现如果在
sum.rb
的每个puts
之后添加STDOUT.flush
,robot.rb
可以运行。但实际上我们不能相信sum.rb
的作者可以添加STDOUT.flush
,对吧?
谢谢你的时间!
终于想出了怎么做。使用write_nonblock
和readpartial
。您必须注意的是,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.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("10n")
elsif line.strip == "Enter number B"
stdin.write("30n")
else
puts line
end
end
end
我的 Ruby 版本是 3.0.2。