如何捕获popen4找不到的命令

chr*_*san 2 ruby rake ruby-on-rails popen

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

在下面你可以运行一个好的任务,糟糕的任务和一个虚假的任务来查看差异.我正在以新鲜rails new apppopen4宝石做这件事

#!/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
Run Code Online (Sandbox Code Playgroud)

以下是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
Run Code Online (Sandbox Code Playgroud)

Ric*_*ond 5

先说几点.

  1. 你不实际使用popen4宝石-这是周围的包装OPEN4宝石(如果你在Unix系统上运行,至少) -您使用的OPEN4直接宝石.如果你想使用popen4,你会这样称呼它:

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

例如:

>  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
Run Code Online (Sandbox Code Playgroud)

在这里,exec试图直接执行不存在的命令'wibble' - 因此异常.

> fork { exec "wibble &2>1" }
 => 1572 
> sh: wibble: command not found
Run Code Online (Sandbox Code Playgroud)

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

> fork { exec "sh -c 'wibble -abc -def'" }
Run Code Online (Sandbox Code Playgroud)

无论如何,了解Kernel #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
Run Code Online (Sandbox Code Playgroud)

更新

有趣.如果从stderr读取,Open4.popen也将返回127退出状态.所以,没有必要使用popen gem.

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