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

Что меня смутило:

  1. robot_2.rb это не то же самое, что взаимодействовать с оболочкой, это больше похоже на то, что нужно оболочке, потому что я просто знаю Что если программе нужно много входных данных, а мы не можем предсказать порядок?

  2. Я узнал, если 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.

Другие вопросы по тегам