Blog.GC

在Ruby中执行其他程序

| Comments

今天想用ruby写一个命令行的自动化执行脚本,我们知道ruby常用的用于执行shell的函数有 Kernel#execsystembacktick(`)IO#popenOpen3#popen3,但是这次 似乎都没用,首先Kernel#execsystembacktick(`)不能提供输入。但是在使用 IO#popenOpen3#popen3,却发现无法获得标准输出。

考虑如下代码片段

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
IO.popen(comm) do |pipe|
  line = pipe.gets
  while !line.nil?  
    puts line
    if line.start_with? 'something'
      pipe.puts 'result'
    end
    line = pipe.gets
  end
end
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Open3.popen3(comm) do |i, o, e, thr|
  line = o.gets
  while !line.nil?  
    puts line
    puts '==='
    if line.start_with? 'something'
      i.puts 'result'
    end
    line = o.gets
  end
end

结果却发现代码执行的时候卡死,没有任何输出。仔细思考一下执行的过程,事实上上面的代码已经构成了 死锁,我们的代码在等待子进程输出管道的内容,但是子进程却在等待输入的内容,造成了死锁。然而, 如果使用IO#close_write来关闭输入管道以避免死锁的话,有无法在后续的步骤中进行输入操作。

所以说前述的这些方法只适合“一次输入,获取输出,退出”这样的程序,对于高交互性的程序就爱莫能助了。 所幸ruby提供了一个名为pty的标准库(文档), 这个库似乎并不常用,不过这个pty库提供了一个BSD-PTY设备的接口,通过pty,我们可以模拟一个真实的终端设备。 关于pty的详细描述,可以参见维基百科

回到我们的代码,我们使用pty改写如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env ruby
require "pty"

PTY.spawn(comm) do |output, input, pid|
  begin
    process_end = false
    buffer = ''
    begin
      output.readpartial(1024, buffer)
    rescue EOFError => e
      process_end = true
      buffer = ''
    end
    puts buffer unless buffer == ''
    if buffer =~ /some_condition/
      # do something
    end
  end until process_end
end

在上述代码中标准错误输出和标准输出被合并在了一起。 值得注意的是,由于子进程是独立并且异步执行的,所以我们需要在读取输出管道的时候做一些判断, 以确定输出是否结束,不然还是会造成死锁。

Comments

comments powered by Disqus