“Який у рубіні”: Перевірка, чи існує програма в $ PATH від ruby


76

мої сценарії значною мірою покладаються на зовнішні програми та сценарії. Мені потрібно бути впевненим, що існує програма, яку мені потрібно викликати. Вручну я б перевірив це, використовуючи 'which' у командному рядку.

Чи існує еквівалент File.exists?для речей у $PATH?

(так, я думаю, я міг би проаналізувати, %x[which scriptINeedToRun]але це не супер елегантно.

Дякую! янник


ОНОВЛЕННЯ: Ось рішення, яке я зберіг:

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

ОНОВЛЕННЯ 2: з’явилося кілька нових відповідей - принаймні деякі з них пропонують кращі рішення.

Оновлення 3: дорогоцінний камінь ptools додає метод "який" до класу File.


Я щойно перевірив цей метод, він не працює. Команда whichкоманди в методі поверне або 1, якщо команда commandне існує, або 0, якщо команда commandіснує. Тож, щоб метод працював, слід замінити 127 на 1
Роберт Ауді

Рішення буде працювати лише в системах unix, де whichприсутня команда . Це виключає Windows та деякі інші системи. Пам'ятайте, що Windows все ще активно використовується серед розробників Ruby; див. моє рішення для справжньої крос-платформної команди.
mislav

3
Ваша відповідь на редагування не є безпечною - її можна ввести з кодом типу "; rm-rf".
lzap

1
imho відповідь від NARKOZ ідеальна! find_executable
awenkhh

1
ptoolsПерлина розчину для мене прекрасно спрацювала!
Арнлен

Відповіді:


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)?
користувач137369

80

Використовуйте 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
Єдиний невеликий підступ для Windows - це те, що за замовчуванням буде вказано список виконуваних розширень із вікна, в якому створена ваша копія ruby, а не локальний список. MakeMakefile::CONFIG["EXECUTABLE_EXTS"] = ENV['PATHEXT'].split(';').join(' ')повинен це виправити.
Matt

23
Виклик mkmf забруднює ваші каталоги файлами mkmf.log.
maasha

6
Ви можете виправити реєстратор MakeMakefile, щоб записати файл журналу в рубіновий еквівалент, /dev/nullякщо ви хочете уникнути створення фізичних файлів журналу. Дивіться цей суть для прикладу: gist.github.com/mnem/2540fece4ed9d3403b98
mnem

6
Вимагати, mkmfяк правило, погана ідея, і його не рекомендують розробники ruby ​​(див. Bugs.ruby-lang.org/issues/12370#note-4 ) - він призначений лише для використання в extconf.rb
fabi

3
Також require 'mkmf'забруднює ваш глобальний простір імен своїми функціями. Це, мабуть, добре в сценарії, але жахлива практика в більш широкому додатку.
ejoubaud,

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

Спочатку взято з концентратора , який використовувався type -tзамість whichхоч (і який не вдався як для zsh, так і для bash).


4
яка є більш широко доступною на платформах * nix, але не повертає ненульовий статус виходу на всіх платформах, коли нічого не знайдено. command -vє стандартом posix і є більш надійним на платформах posix.
Ry Biesemeyer

1
Чому б вам не перевірити which #{name}.empty??
Мохсен,

1
Тому що на SmartOS (та інших ароматах Illumnos) 'which command' повертає такий рядок, коли НЕ знаходить нічого: > which fooповертаєno foo in /opt/local/bin /opt/local/sbin /usr/bin /usr/sbin
Костянтин Гредескул

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

Тут використовується недокументований метод # find_executable0, викликаний MakeMakefile # find_executable, щоб повернути шлях без захаращення стандартного виводу. Метод #which також тимчасово перенаправляє файл журналу mkmf на / dev / null, щоб запобігти захаращенню поточного робочого каталогу "mkmf.log" або подібним.


1
Після того, як хтось використовував це рішення під час роботи, я виявив, що існує проблема, коли mkmfбуде зіткнення з ffiдорогоцінними каменями на основі деяких визначень методів (які, як я припускаю, обидва дорогоцінні камені визначаються в кореневому просторі імен). Це обмежує корисність рішення.
Ніл Слейтер,

Це не є безпечним для потоку, і він використовує внутрішній 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

А як щодо розширення шляху? Можливо, також краще використовувати 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.
mislav


2

Я хотів би додати, що whichпрапорець приймає -sтихий режим, який лише встановлює прапор успіху, усуваючи необхідність перенаправляти вихідні дані.


У моїй системі whichне приймається прапорець -s, це десь вказано?
ComputerDruid

man whichрозповів мені про мою систему. Але на моїй робочій сторінці написано: "Деякі оболонки можуть надавати вбудовану команду, яка є аналогічною або ідентичною цій утиліті. Зверніться до сторінки вбудованої (1) інструкції". YMMV.
olleolleolle

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', і у вас це є. Я спробував тут відредагувати вашу відповідь з цією метою, але вона була відхилена, оскільки, мабуть, я «змінював ваше значення».
Джефф Ніксон,

У Linux я використовую: which #{command}существует = .размер.> (0) На жаль, whichце не команда POSIX і тому поводиться по-різному на Mac, BSD тощо (тобто видає помилку, якщо команду не знайдено). Ідеальним рішенням є використання sh -c 'command -v #{command}'.size.> (0) Це sh -cнеобхідно, оскільки інакше ruby ​​не зможе отримати доступ до вбудованих функцій. Але це command -vбув би спосіб POSIX для цього.
Джефф Ніксон,

1

Рішення, засноване на rogeriovl, але повноцінна функція з тестом виконання, а не тестом існування.

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

Працюватиме лише для UNIX (Windows не використовує двокрапку як роздільник)


Добре, дякую. Але зауважте, це буде працювати лише для UNIX. Чудово з видаленням розмови, але нотатка там повинна залишатися.
lzap

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
Це може бути довго, краще використовувати систему ("which" + c) тоді.
філант

2
дзвінок cmdExists?('rm -rf ~'). Також рубіновою конвенцією є cmd_exists?
називання

Дійсно відмінна порада. Це може навіть стерти ваш HDD. НЕ РЕКОМЕНДОВАНО!
lzap
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.