Runy Open3.popen3从命令行将输入输入到子流程中



目标:我正在用ruby编写一个工作流命令行程序,该程序按顺序执行UNIXshell上的其他程序,其中一些程序需要用户输入。

问题:尽管由于Nick Charlton的这篇有用的博客文章,我可以成功地处理stdoutstderr,但我仍然坚持捕获用户输入并通过命令行将其传递到子进程中。代码如下:

方法

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 

调用方法

这个例子调用shell命令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方法处理来自命令行的用户输入,这样我就可以允许用户将数据输入到从我的程序调用的各种子命令序列中。

以下是应该起作用的东西:

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

在阅读了大量关于STDIN的内容以及一些古老的尝试和错误之后,我发现一个实现与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的信息,那么值得阅读以下问题的答案——Ruby中的stdin和$stdin之间有什么区别?

相关内容

  • 没有找到相关文章

最新更新