Goal: I am writing a workflow command-line program in ruby that sequentially executes other programs on the UNIX shell, some of which require the user to enter input.
Problem: Although I can successfully handle the stdout
and stderr
thanks to this helpful blog post by Nick Charlton, I am however stuck on capturing user input and passing it into the sub-processes via the command line. The code is as follows:
Method
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
Calling the method
This example calls the shell command units
which prompts the user to enter a unit of measurement and then prompts for a unit to convert to. This is how it would look in the shell
> units
586 units, 56 prefixes # stdout
You have: 1 litre # user input
You want: gallons # user input
* 0.26417205 # stdout
/ 3.7854118 # stdout
When I run this from my program I expect to be able to interact with it in exactly the same way.
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
Note: Calling the run
method this way allows the user to be able to parse the output, control process flow and add custom logging.
From what I've gathered about STDIN, the snippet below is as close as I've come in understanding how to handle STDIN, there are clearly some gaps in my knowledge because I'm still unsure how to integrate this into my run
method above and pass the input into the child process.
# 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
Summary: I wish to understand how to handle user input from the command-line via the Open3.popen3
method so that I can allow users to enter data into various sequence of sub-commands called from my program.