使用popen3无法正确处理STDOUT和STDERR输出?

时间:2013-11-25 04:25:28

标签: ruby

我正在编写一个执行shell命令的函数,并返回其退出代码STDOUT和STDERR。

问题是,此函数无法正确捕获STDOUT和STDERR输出。

def sh(*args)
  options = args[-1].respond_to?(:to_hash) ? args.pop.to_hash: {}
  options = { :timeout => 0, :sudo => false }.merge(options)
  cmd = options[:sudo] == false ? args[0] : "sudo " << args[0]

  begin
    stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)
    pid = wait_thr[:pid]
    out_buf = ""
    err_buf = ""
    start = Time.now

    # Manually ping the process per 0.2 second to check whether the process is alive or not
    begin
      out_buf << stdout.read_nonblock(4096)
      err_buf << stderr.read_nonblock(4096)
      # kill the process if it timeouts
      if options[:timeout] != 0 && (Time.now - start) > options[:timeout]
        Process.kill("KILL", pid)
        Process.detach(pid)
        raise RuntimeError, "process with pid #{pid} timed out with #{options[:timeout]} seconds."
      end
      sleep 0.2
    rescue IO::WaitReadable, EOFError
    end while wait_thr.alive?

  rescue => e
    NtfLogger.warn("sh '#{args}' executed with failure: #{e}")

  ensure
    if wait_thr.nil?
      return 1, out_buf, err_buf
    else
      return wait_thr.value.exitstatus, out_buf, err_buf
    end
  end
end # end of sh

有人可以帮我弄清问题是什么吗?

1 个答案:

答案 0 :(得分:0)

我对popen3文档的理解是,最好在一个区块内进行处理:

Open3.popen3([env,] cmd... [, opts]) do |stdin, stdout, stderr, wait_thr|
  pid = wait_thr.pid # pid of the started process.
  ...
  exit_status = wait_thr.value # Process::Status object returned.
end

以非阻止形式,文档指出必须关闭流:

stdin, stdout, stderr, wait_thr = Open3.popen3([env,] cmd... [, opts])
pid = wait_thr[:pid]  # pid of the started process.
...
stdin.close  # stdin, stdout and stderr should be closed explicitly in this form.
stdout.close
stderr.close
exit_status = wait_thr.value  # Process::Status object returned.

http://www.ruby-doc.org/stdlib-2.0.0/libdoc/open3/rdoc/Open3.html#method-c-popen3

最后,FWIW,这里是capture3的包装,我在最后使用它。您可以轻松扩展它以添加sudo选项,以防sh实用程序中与线程相关的部分不重要:

#
# Identical to Open3.capture3, except that it rescues runtime errors
#
# @param env optional (as `Kernel.system')
# @param *cmd the command and its (auto-escaped) arguments
# @param opts optional a hash of options (as `Kernel.system')
#
# @return [stdout, stderr, success] | [$/, $/, nil] on error
#
def system3(*cmd)
  begin
    stdout, stderr, status = Open3.capture3(*cmd)
    [stdout, stderr, status.success?]
  rescue
    [$/, $/, nil]
  end
end