"который в рубине": проверка наличия программы в $PATH из ruby
мои скрипты сильно зависят от внешних программ и скриптов.
Я должен быть уверен, что существует программа, которую мне нужно вызвать.
Вручную, я бы проверил это, используя "which" в командной строке.
Существует ли эквивалент File.exists?
для вещей в $PATH
?
(да, я думаю, я мог бы разобрать %x[which scriptINeedToRun]
, но это не очень элегантно.
Спасибо!
Янник
UPDATE: Здесь решение, которое я сохранил:
def command?(command)
system("which #{ command} > /dev/null 2>&1")
end
ОБНОВЛЕНИЕ 2: Пришло несколько новых ответов - по крайней мере, некоторые из них предлагают лучшие решения.
Обновление 3: ptools gem добавляет метод "which" в класс File.
Ответы
Ответ 1
Истинное кросс-платформенное решение, корректно работает в 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 { |ext|
exe = File.join(path, "#{cmd}#{ext}")
return exe if File.executable?(exe) && !File.directory?(exe)
}
end
return nil
end
Это не использует sniffing операционной системы и уважает $PATHEXT, в котором перечислены допустимые расширения файлов для исполняемых файлов в Windows.
Обрезание до which
работает во многих системах, но не во всех.
Ответ 2
Используйте find_executable
метод mkmf
, который включен в stdlib.
require 'mkmf'
find_executable 'ruby'
#=> "/Users/narkoz/.rvm/rubies/ruby-2.0.0-p0/bin/ruby"
find_executable 'which-ruby'
#=> nil
Ответ 3
def command?(name)
`which #{name}`
$?.success?
end
Первоначально взято из hub, в котором вместо which
использовался type -t
вместо which
(и который не прошел как для zsh, так и для bash для меня).
Ответ 4
Вы можете получить доступ к переменным системной среды с помощью хэша ENV:
puts ENV['PATH']
Он вернет PATH в вашей системе. Поэтому, если вы хотите знать, существует ли программа nmap
, вы можете сделать это:
ENV['PATH'].split(':').each {|folder| puts File.exists?(folder+'/nmap')}
Это напечатает true
, если файл был найден или false
в противном случае.
Ответ 5
Использовать 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
Это использует недокументированный метод # find_executable0, вызываемый MakeMakefile # find_executable, чтобы вернуть путь без стандартного вывода загромождения. Метод #whyst также временно перенаправляет файл mkmf в /dev/null, чтобы предотвратить загромождение текущего рабочего каталога с помощью "mkmf.log" или аналогичного.
Ответ 6
Вот что я использую. Это нейтральная платформа (File::PATH_SEPARATOR
is ":"
в Unix и ";"
в Windows), только ищет файлы программ, которые фактически исполняются эффективным пользователем текущего процесса, и завершается, как только программа найдена:
##
# Returns +true+ if the +program+ executable is found in the user path.
def has_program?(program)
ENV['PATH'].split(File::PATH_SEPARATOR).any? do |directory|
File.executable?(File.join(directory, program.to_s))
end
end
Ответ 7
Я хотел бы добавить, что which
принимает флаг -s
для бесшумного режима, который устанавливает только флаг успеха, устраняя необходимость перенаправления вывода.
Ответ 8
Это улучшенная версия, основанная на ответе @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
Ответ 9
Был GEM под названием which_ruby
, который был реализацией pure-Ruby.
Он больше не доступен.
Однако я нашел эту чистую-Ruby альтернативную реализацию.
Ответ 10
Решение основано на rogeriovl, но полная функция с тестом выполнения, а не тестом на существование.
def command_exists?(command)
ENV['PATH'].split(':').each {|folder| File.executable?(File.join(folder, command))}
end
Будет работать только для UNIX (Windows не использует двоеточие в качестве разделителя)
Ответ 11
В 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 для этого.
Ответ 12
У меня есть это:
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
Ответ 13
Это настройка ответа 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
Ответ 14
для 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 в вашем фактическом использовании.
Ответ 15
#####################################################
# add methods to see if there an executable that 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
Ответ 16
Не так элегантно, но он работает:).
def cmdExists?(c)
system(c + " > /dev/null")
return false if $?.exitstatus == 127
true
end
Предупреждение. Это НЕ рекомендуется, опасный совет!