“在ruby中”:检查程序在$ PATH中是否存在于ruby中


76

我的脚本在很大程度上依赖于外部程序和脚本。我需要确保我需要调用的程序存在。手动地,我会在命令行中使用“哪个”来检查。

是否有等同File.exists?$PATH

(是的,我想我可以解析 %x[which scriptINeedToRun]但这不是超级优雅。

谢谢!扬尼克


更新:这是我保留的解决方案:

 def command?(command)
       system("which #{ command} > /dev/null 2>&1")
 end

更新2:一些新的答案出现了-至少其中一些提供了更好的解决方案。

更新3:ptools gem向File类添加了一个“ which”方法。


我只是测试了这种方法,所以没有用。which如果该命令command不存在,则该方法中的命令command将返回1;如果该命令存在,则将返回0 command。因此,要使该方法起作用,您应该将127替换为1
罗伯特·奥迪

该解决方案仅在which存在命令的UNIX系统上有效。这不包括Windows和某些其他系统。请记住,Windows在Ruby开发人员中仍然大量使用。请参阅我的解决方案以获取真正的跨平台命令。
米斯拉夫2011年

3
您的编辑答案不安全-可以注入“; rm-rf”之类的代码。
lzap 2012年

1
恕我直言,NARKOZ的答案是完美的!find_executable
awenkhh 2014年

1
解决方案ptools宝石对我来说非常完美!
2015年

Answers:


123

真正的跨平台解决方案,在Windows上可以正常运行:

# Cross-platform way of finding an executable in the $PATH.
#
#   which('ruby') #=> /usr/bin/ruby
def which(cmd)
  exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
  ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
    exts.each do |ext|
      exe = File.join(path, "#{cmd}#{ext}")
      return exe if File.executable?(exe) && !File.directory?(exe)
    end
  end
  nil
end

这不使用主机操作系统嗅探,而是遵守$ PATHEXT,它列出了Windows上可执行文件的有效文件扩展名。

脱壳出来which的作品在许多系统上,但不是全部。


1
注意:当绝对路径或相对路径包含为cmd/bin/sh..\foo)时,当前答案也无法处理(合理的?)边缘情况。

为什么要!File.directory?(exe)代替File.file?(exe)
user137369

79

使用stdlib中包含的find_executable方法mkmf

require 'mkmf'

find_executable 'ruby'
#=> "/Users/narkoz/.rvm/rubies/ruby-2.0.0-p0/bin/ruby"

find_executable 'which-ruby'
#=> nil

3
Windows上唯一的小问题是,它将默认为构建您的ruby副本的框中的可执行扩展名列表,而不是本地列表。MakeMakefile::CONFIG["EXECUTABLE_EXTS"] = ENV['PATHEXT'].split(';').join(' ')应该解决这个问题。
马特

23
调用mkmf会使用mkmf.log文件污染目录。
maasha 2014年

6
您可以修补MakeMakefile记录器,以将日志文件写入等效于ruby的目录(/dev/null如果要避免创建物理日志文件)。有关示例,请参见以下摘要
mnem 2014年

6
要求mkmf通常不是一个好主意,并且被ruby开发人员建议(请参见bugs.ruby-lang.org/issues/12370#note-4)-它仅用于extconf.rb

3
还会require 'mkmf'用其功能污染全局名称空间。在脚本中可能很好,但在更广泛的应用程序中却很糟糕。
ejoubaud

17
def command?(name)
  `which #{name}`
  $?.success?
end

最初是从hub那里获取的,它使用了type -t替代方法(which但对我来说zsh和bash都失败了)。


4
它在* nix平台上更广泛地提供,但是当什么都没找到时,它不会在所有平台上返回非零退出状态。command -v是posix标准,在posix平台上更可靠。
Ry Biesemeyer

1
你为什么不检查which #{name}.empty?
Mohsen

1
因为在SmartOS(以及其他类型的Illumnos)上,“哪条命令”在找不到任何内容时会返回以下字符串: > which foo返回no foo in /opt/local/bin /opt/local/sbin /usr/bin /usr/sbin
Konstantin Gredeskoul 2014年

6

在禁用日志记录的情况下使用MakeMakefile#find_executable0

已经有很多好的答案,但是这是我使用的:

require 'mkmf'

def set_mkmf_log(logfile=File::NULL)
  MakeMakefile::Logging.instance_variable_set(:@logfile, logfile)
end

# Return path to cmd as a String, or nil if not found.
def which(cmd)
  old_mkmf_log = MakeMakefile::Logging.instance_variable_get(:@logfile)
  set_mkmf_log(nil)
  path_to_cmd = find_executable0(cmd)
  set_mkmf_log(old_mkmf_log)
  path_to_cmd
end

这将使用MakeMakefile#find_executable调用的未记录的#find_executable0方法来返回路径,而不会混淆标准输出。#which方法还将临时将mkmf日志文件重定向到/ dev / null,以防止使用“ mkmf.log”或类似内容使当前工作目录混乱。


1
我发现,在有人在工作中使用此解决方案后,出现了一个问题,即在某些方法定义(我假设这两个gem都在根名称空间中定义)上mkmfffi基于-gem发生冲突。这确实限制了该解决方案的实用性。
尼尔·斯莱特

这不是线程安全的,它使用的内部API可能会发生变化。

5

您可以使用ENV哈希访问系统环境变量:

puts ENV['PATH']

它将返回系统上的PATH。因此,如果您想知道程序是否nmap存在,可以执行以下操作:

ENV['PATH'].split(':').each {|folder| puts File.exists?(folder+'/nmap')}

true如果找到文件,则将打印此文件false


1
您可能还应该检查用户是否可执行文件:File.exists?(...)和File.executable?(...)。+1。
liwp 2010年

扩展路径呢?也许最好使用File.join或Pathname。又为什么不使用哪个呢?这是一个非常好的工具,它可以完成工作。
tig

1
我第二个@kolrie; 这不是跨平台的。查看我的解决方案
米斯拉夫

3

这就是我正在使用的。这是与平台无关的(File::PATH_SEPARATOR":"Unix和";"Windows上),只有尽快程序时,查找程序文件,实际上是由当前进程的有效用户可执行文件,并终止:

##
# Returns +true+ if the +program+ executable is found in the user's path.
def has_program?(program)
  ENV['PATH'].split(File::PATH_SEPARATOR).any? do |directory|
    File.executable?(File.join(directory, program.to_s))
  end
end

3
这不符合$ PATHEXT环境变量。
米拉夫斯


2

我想添加一个which带有-s静音模式标志的标志,该标志仅设置成功标志,而无需重定向输出。


在我的系统which上,不接受-s标志,该标志是否在某处指定?
ComputerDruid

man which告诉我有关我的系统的信息。但是,我的手册页说:“某些shell可能会提供一个与此命令实用程序相似或相同的内置命令。请查阅buildin(1)手册页。” YMMV。
olleolleolle 2013年

2

这是基于@mislav的答案的改进版本。这将允许任何类型的路径输入,并且严格遵循如何cmd.exe选择要在Windows中执行的文件。

# which(cmd) :: string or nil
#
# Multi-platform implementation of "which".
# It may be used with UNIX-based and DOS-based platforms.
#
# The argument can not only be a simple command name but also a command path
# may it be relative or complete.
#
def which(cmd)
  raise ArgumentError.new("Argument not a string: #{cmd.inspect}") unless cmd.is_a?(String)
  return nil if cmd.empty?
  case RbConfig::CONFIG['host_os']
  when /cygwin/
    exts = nil
  when /dos|mswin|^win|mingw|msys/
    pathext = ENV['PATHEXT']
    exts = pathext ? pathext.split(';').select{ |e| e[0] == '.' } : ['.com', '.exe', '.bat']
  else
    exts = nil
  end
  if cmd[File::SEPARATOR] or (File::ALT_SEPARATOR and cmd[File::ALT_SEPARATOR])
    if exts
      ext = File.extname(cmd)
      if not ext.empty? and exts.any?{ |e| e.casecmp(ext).zero? } \
      and File.file?(cmd) and File.executable?(cmd)
        return File.absolute_path(cmd)
      end
      exts.each do |ext|
        exe = "#{cmd}#{ext}"
        return File.absolute_path(exe) if File.file?(exe) and File.executable?(exe)
      end
    else
      return File.absolute_path(cmd) if File.file?(cmd) and File.executable?(cmd)
    end
  else
    paths = ENV['PATH']
    paths = paths ? paths.split(File::PATH_SEPARATOR).select{ |e| File.directory?(e) } : []
    if exts
      ext = File.extname(cmd)
      has_valid_ext = (not ext.empty? and exts.any?{ |e| e.casecmp(ext).zero? })
      paths.unshift('.').each do |path|
        if has_valid_ext
          exe = File.join(path, "#{cmd}")
          return File.absolute_path(exe) if File.file?(exe) and File.executable?(exe)
        end
        exts.each do |ext|
          exe = File.join(path, "#{cmd}#{ext}")
          return File.absolute_path(exe) if File.file?(exe) and File.executable?(exe)
        end
      end
    else
      paths.each do |path|
        exe = File.join(path, cmd)
        return File.absolute_path(exe) if File.file?(exe) and File.executable?(exe)
      end
    end
  end
  nil
end

@Barry决定什么是不必要的不​​是您。
konsolebox

2

我有这个:

def command?(name)
  [name,
   *ENV['PATH'].split(File::PATH_SEPARATOR).map {|p| File.join(p, name)}
  ].find {|f| File.executable?(f)}
end

适用于完整路径以及命令:

irb(main):043:0> command?("/bin/bash")
=> "/bin/bash"
irb(main):044:0> command?("bash")
=> "/bin/bash"
irb(main):006:0> command?("bush")
=> nil

1

在linux上,我使用:

exists = `which #{command}`.size.>(0)

不幸的是,which它不是POSIX命令,因此在Mac,BSD等设备上的行为有所不同(即,如果未找到该命令,则会引发错误)。也许理想的解决方案是使用

`command -v #{command}`.size.>(0)  # fails!: ruby can't access built-in functions

但这失败了,因为ruby似乎无法访问内置函数。但这command -v将是POSIX的方法。


2
这是正确的。您只需要sh -c 'command -v #command',您就可以做到。我试图在此处编辑您的答案以达到这种效果,但被拒绝了,因为显然我在“更改您的意思”。
Geoff Nixon

在Linux上,我使用:which #{command}exist = .size。>(0)不幸的是,which它不是POSIX命令,因此在Mac,BSD等设备上的行为有所不同(即,如果找不到该命令,则会引发错误)。理想的解决方案是使用 sh -c 'command -v #{command}'.size。>(0)这sh -c是必需的,因为否则ruby将无法访问内置函数。但这command -v将是POSIX的方法。
Geoff Nixon

1

基于rogeriovl的解决方案,但具有执行测试而非存在测试的完整功能。

def command_exists?(command)
  ENV['PATH'].split(':').each {|folder| File.executable?(File.join(folder, command))}
end

仅适用于UNIX(Windows不使用冒号作为分隔符)


好,谢谢。但是请注意,这仅适用于UNIX。去除杂物就可以了,但是音符应该留在那里。
lzap 2013年

0

这是rogeriopvl的回答的一个调整,使其跨平台:

require 'rbconfig'

def is_windows?
  Config::CONFIG["host_os"] =~ /mswin|mingw/
end

def exists_in_path?(file)
  entries = ENV['PATH'].split(is_windows? ? ";" : ":")
  entries.any? {|f| File.exists?("#{f}/#{file}")}
end

0

对于jruby,任何依赖于 mkmf可能不起作用,因为它具有C扩展名。

对于jruby,以下是检查路径上是否可执行文件的简便方法:

main » unix_process = java.lang.Runtime.getRuntime().exec("git status")
=> #<Java::JavaLang::UNIXProcess:0x64fa1a79>
main » unix_process.exitValue()
=> 0
main »

如果可执行文件不存在,则会引发运行时错误,因此您可能希望在实际使用情况的try / catch块中执行此操作。


0
#####################################################
# add methods to see if there's an executable that's executable
#####################################################
class File
  class << self
    ###########################################
    # exists and executable
    ###########################################
    def cmd_executable?(cmd)
      !ENV['PATH'].split(':').select { |f| executable?(join(f, cmd[/^[^ \n\r]*/])) }.empty?
    end
  end
end

-8

没有那么优雅,但它有效:)。

def cmdExists?(c)
  system(c + " > /dev/null")
  return false if $?.exitstatus == 127
  true
end

警告建议这样做,危险的建议!


1
可能会比较长,最好使用system(“ which” + c)。
philant 2010年

2
打电话cmdExists?('rm -rf ~')。另外,ruby约定是命名方法,例如cmd_exists?
tig

真的很棒的建议。这甚至可以擦除硬盘。不建议!
lzap 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.