2015-04-18 3 views
3

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

Проблема: Хотя я могу успешно справиться с stdout и stderr благодаря этой полезной blog post по Nick Charlton, я однако застрял на захват пользовательского ввода и передачи его в подпроцессов с помощью командной строки. Код следующим образом:

Метод

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 так, что я могу позволить пользователям вводить данные в различные последовательности суб-команд, вызываемых из моей программы.

+0

Я также люблю знать, как это сделать –

ответ

1

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

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 не отображаются в порядке, это ограничение, которое я еще не решил.

Если вы заинтересованы в получении дополнительной информации о стандартном вводе это стоит прочитать следующий ответ на вопрос - What is the difference between STDIN and $stdin in Ruby?

0

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

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.

+0

Это не похоже на работу, я боюсь, я протестировал его, вызвав метод 'CMD.run 'units'' вам удалось заставить его работать? – ImaginateWayne

+0

Да, это работает для меня. Заметьте, я использую Ruby 2.1.5. –

Смежные вопросы