如何捕获popen4找不到的命令

时间:2013-02-16 23:51:38

标签: ruby-on-rails ruby rake popen

我正在使用popen4来捕获stdout,stderr和命令行的退出状态。只要我能抓住上述三件事,我就不会受到popen4的束缚。目前我还没有找到捕获命令未找到错误的好方法。我可以在预设的任务中做which cmd,但希望有内置的东西。

在下面你可以运行一个好的任务,糟糕的任务和一个虚假的任务来查看差异。我正在使用rails new app gem

的新popen4进行此操作
#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

require File.expand_path('../config/application', __FILE__)

require 'open4'

# returns exit status 0, all is good
task :convert_good do
  puts "convert good"
  `wget https://www.google.com/images/srpr/logo3w.png`
  status = Open4.popen4("convert logo3w.png output.jpg") do |pid, stdin,stdout,stderr|
    stdin.close
    puts "stdout:"
    stdout.each_line { |line| puts line }
    puts "stderr: #{stderr.inspect}"
    stderr.each_line { |line| puts line }
  end
  puts "status: #{status.inspect}"
  puts "exit:   #{status.exitstatus}"
end

# returns exit status 1, we messed up our command
task :convert_bad do
  puts "convert bad"
  status = Open4.popen4("convert logo3w-asdfasdf.png output.jpg") do |pid, stdin,stdout,stderr|
    stdin.close
    puts "stdout:"
    stdout.each_line { |line| puts line }
    puts "stderr: #{stderr.inspect}"
    stderr.each_line { |line| puts line }
  end
  puts "status: #{status.inspect}"
  puts "exit:   #{status.exitstatus}"
end

# I want this to return exit code 127 for command not found
task :convert_none do
  puts "convert bad"
  status = Open4.popen4("convert_not_installed") do |pid, stdin,stdout,stderr|
    stdin.close
    puts "stdout:"
    stdout.each_line { |line| puts line }
    puts "stderr: #{stderr.inspect}"
    #it doesnt like stderr in this case
    #stderr.each_line { |line| puts line }
  end
  puts "status: #{status.inspect}"
  puts "exit:   #{status.exitstatus}"
end

以下是3个本地输出

# good
stdout:
stderr: #<IO:fd 11>
status: #<Process::Status: pid 17520 exit 0>
exit:   0

# bad arguments
convert bad
stdout:
stderr: #<IO:fd 11>
convert: unable to open image `logo3w-asdfasdf.png': No such file or directory @ blob.c/OpenBlob/2480.
convert: unable to open file `logo3w-asdfasdf.png' @ png.c/ReadPNGImage/2889.
convert: missing an image filename `output.jpg' @ convert.c/ConvertImageCommand/2800.
status: #<Process::Status: pid 17568 exit 1>
exit:   1

# fake command not found, but returns exit 1 and stderr has no lines
convert bad
stdout:
stderr: #<IO:fd 11>
status: #<Process::Status: pid 17612 exit 1>
exit:   1

1 个答案:

答案 0 :(得分:5)

首先要说几点。

  1. 你实际上并没有使用 popen4 gem - 它是 open4 gem的包装器(如果你在Unix系统上运行,在至少) - 你直接使用 open4 gem。如果您想使用 popen4 ,您可以这样称呼它:

    status = POpen4.popen4('cmd') do |stdout, stderr, stdin, pid|
      # ...
    end
    
  2. popen4 方法最终通过内核#exec 方法执行指定的命令,其行为取决于它是否确定给定的命令应该是在shell中运行或不运行。 (你可以看到http://www.ruby-doc.org/core-1.9.3/Kernel.html#method-i-exec,但它并不是非常有用。源代码是一个更好的选择。)

  3. 例如:

    >  fork { exec "wibble" }
     => 1570 
    > (irb):56:in `exec': No such file or directory - wibble (Errno::ENOENT)
        from (irb):56:in `irb_binding'
        from (irb):56:in `fork'
        from (irb):56:in `irb_binding'
        from /Users/evilrich/.rvm/rubies/ree-1.8.7-2011.03/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'
        from :0
    

    这里, exec 试图直接执行不存在的命令'wibble' - 因此是例外。

    > fork { exec "wibble &2>1" }
     => 1572 
    > sh: wibble: command not found
    

    在这里, exec 看到我正在使用重定向,因此在shell中执行了我的命令。区别?我在STDERR上遇到错误,也没有例外。您还可以通过在要执行的命令中指定shell来强制使用shell:

    > fork { exec "sh -c 'wibble -abc -def'" }
    

    无论如何,了解内核#exec 的行为可能有助于让 popen4 方法按照您希望的方式运行。

    要回答你的问题,如果我使用 popen4 gem并以这样的方式构造命令(通过exec的规则)它将在shell中运行或者如果我使用“sh - c ...“在我自己的命令中,然后我得到了我认为你正在寻找的那种行为:

    > status = POpen4.popen4("sh -c 'wibble -abc -def'") {|stdout, stderr, stdin, pid| puts "Pid: #{pid}"}
    Pid: 1663
     => #<Process::Status: pid=1663,exited(127)> 
    > puts status.exitstatus
    127
    

    <强>更新

    有趣。 Open4.popen 还会返回127退出状态,如果您从stderr读取。因此,不需要使用 popen gem。

    > status = Open4.popen4("sh -c 'wibble -abc -def'") {|pid, stdin, stdout, stderr| stderr.read }
     => #<Process::Status: pid 1704 exit 127>