Runy Open3.popen3 Ввод ввода в подпроцесс из командной строки

Цель: я пишу программу командной строки рабочего процесса на ruby, которая последовательно выполняет другие программы в оболочке UNIX, некоторые из которых требуют ввода пользователем ввода.

Проблема: хотя я могу успешно справиться с stdout а также stderr благодаря этому полезному сообщению в блоге nickcharlton, я, однако, застрял на захвате ввода пользователя и передаче его в подпроцессы через командную строку. Код выглядит следующим образом:

метод

module CMD
  def run(cmd, &block)
    Open3.popen3(cmd) do |stdin, stdout, stderr, thread|
      Thread.new do # STDOUT
        until (line = stdout.gets).nil? do
          yield nil, line, nil, thread if block_given?
        end
      end

      Thread.new do # STDERR 
        until (line = stderr.gets).nil? do
          yield nil, nil, line, thread if block_given?
        end
      end

      Thread.new do # STDIN
        # ????? How to handle
      end

      thread.join
    end
  end
end 

Вызов метода

В этом примере вызывается команда оболочки units который предлагает пользователю ввести единицу измерения, а затем предлагает единицы для преобразования. Вот так это будет выглядеть в оболочке

> units
586 units, 56 prefixes        # stdout
You have: 1 litre             # user input
You want: gallons             # user input
* 0.26417205                  # stdout
/ 3.7854118                   # stdout

Когда я запускаю это из своей программы, я ожидаю, что смогу взаимодействовать с ним точно так же.

unix_cmd = 'units'
run unix_cmd do | stdin, stdout, stderr, thread|
  puts "stdout #{stdout.strip}" if stdout
  puts "stderr #{stderr.strip}" if stderr
  # I'm unsure how I would allow the user to
  # interact with STDIN here?
end

Примечание: вызов run Этот метод позволяет пользователю анализировать выходные данные, контролировать процесс и добавлять пользовательские записи в журнал.

Из того, что я собрал о STDIN, приведенный ниже фрагмент настолько близок, насколько я понял, как обращаться со STDIN, в моих знаниях явно есть пробелы, потому что я все еще не уверен, как интегрировать это в мой run метод выше и передать вход в дочерний процесс.

# STDIN: Constant declared in ruby
# stdin: Parameter declared in Open3.popen3
Thread.new do 
    # Read each line from the console
    STDIN.each_line do |line|
       puts "STDIN: #{line}" # print captured input 
       stdin.write line      # write input into stdin
       stdin.sync            # sync the input into the sub process
       break if line == "\n"
    end
end

Резюме: я хочу понять, как обрабатывать пользовательский ввод из командной строки через Open3.popen3 метод, позволяющий пользователям вводить данные в различные последовательности подкоманд, вызываемых из моей программы.

2 ответа

Решение

После долгих чтений о STDIN, а также старых добрых проб и ошибок я обнаружил реализацию, не Charles Finkel ответ Charles Finkel, но с некоторыми тонкими различиями.

require "open3"

module Cmd
  def run(cmd, &block)
    Open3.popen3(cmd) do |stdin, stdout, stderr, thread|
      # We only need to check if the block is provided once
      # rather than every cycle of the loop as we were doing 
      # in the original question.

      if block_given?
        Thread.new do
          until (line = stdout.gets).nil? do
            yield line, nil, thread
          end
        end

        Thread.new do
          until (line = stderr.gets).nil? do
            yield nil, line, thread
          end
        end
      end

      # $stdin.gets reads from the console
      #
      # stdin.puts writes to child process
      #
      # while thread.alive? means that we keep on
      # reading input until the child process ends
      Thread.new do
        stdin.puts $stdin.gets while thread.alive?
      end

      thread.join
    end
  end
end

include Cmd

Вызов метода так:

  run './test_script.sh' do | stdout, stderr, thread|
    puts "#{thread.pid} stdout: #{stdout}" if stdout
    puts "#{thread.pid} stderr: #{stderr}" if stderr
  end

куда test_script.sh как следует:

echo "Message to STDOUT"
>&2 echo "Message to STDERR"
echo "enter username: "
read username
echo "enter a greeting"
read greeting
echo "$greeting $username"
exit 0

Создает следующий успешный результат:

25380 stdout: Message to STDOUT
25380 stdout: enter username:
25380 stderr: Message to STDERR
> Wayne
25380 stdout: enter a greeting
> Hello
25380 stdout: Hello Wayne

Примечание: вы заметите, что stdout и stderr отображаются не по порядку, это ограничение, которое мне еще предстоит решить.

Если вам интересно узнать больше о stdin, стоит прочитать следующий ответ на вопрос: в чем разница между STDIN и $stdin в Ruby?

Вот то, что должно работать:

module CMD
  def run(cmd, &block)
    Open3.popen3(cmd) do |stdin, stdout, stderr, thread|
      Thread.new do # STDOUT
        until (line = stdout.gets).nil? do
          yield nil, line, nil, thread if block_given?
        end
      end

      Thread.new do # STDERR 
        until (line = stderr.gets).nil? do
          yield nil, nil, line, thread if block_given?
        end
      end

      t = Thread.new { loop { stdin.puts gets } }

      thread.join
      t.kill
    end
  end
end 

Я только что добавил две строки к вашему оригиналу run метод: t = Thread.new { loop { stdin.puts gets } }, а также t.kill,

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