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
,