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.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 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
"s 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
Что меня смутило:
robot_2.rb
это не то же самое, что взаимодействовать с оболочкой, это больше похоже на то, что нужно оболочке, потому что я просто знаю Что если программе нужно много входных данных, а мы не можем предсказать порядок?Я узнал, если
STDOUT.flush
были добавлены после каждогоputs
вsum.rb
,robot.rb
мог бежать. Но на самом деле мы не можем доверятьsum.rb
Автор может добавитьSTDOUT.flush
, право?
Спасибо за ваше время!
2 ответа
Наконец разобрался, как это сделать. Использовать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("hello\n")
wait_thr.join
}
Кстати, это не очень потокобезопасно. Я нашел это неоптимизированное, но функциональное решение, которое, надеюсь, будет улучшено в будущем.
Я немного поигрался с ответом @jeff-hykin. Итак, основная задача — отправить данные изsum.rb
в неблокирующем режиме, т.е. используйте:
# 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}"
-- обратите внимание на\n
вSTDOUT.write_nonblock
звонки. Он разделяет строки/строки, которые читаются с помощьюgets
«получить строку» в . Затем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.