2015-04-18 26 views
3

目标:我正在用ruby编写一个工作流命令行程序,它依次执行UNIX shell上的其他程序,其中一些需要用户输入输入。Runy Open3.popen3从命令行输入子进程

问题:虽然我可以成功Nick Charlton处理stdoutstderr感谢这个有用blog post,不过,我被困在捕捉用户输入,并将其传递到子过程通过命令行。的代码如下:

方法

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,明显有在我所知一些差距,因为我仍然不确定如何整合这个放到我上面的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'单元''的方法来测试它,你是否设法使它工作? – ImaginateWayne

+0

是的,这对我有用。请注意,我使用Ruby 2.1.5。 –