web-dev-qa-db-fra.com

"which in Ruby": Vérification de l'existence d'un programme dans $ PATH à partir de ruby

mes scripts reposent largement sur des programmes et des scripts externes ... Je dois être sûr qu'un programme à appeler existe. Manuellement, je vérifierais ceci en utilisant 'qui' dans la ligne de commande.

Existe-t-il un équivalent de File.exists? pour les éléments de $PATH?

(oui je suppose que je pourrais analyser %x[which scriptINeedToRun] mais ce n'est pas très élégant.

Merci! Yannick


UPDATE: Voici la solution que j'ai retenue:

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

MISE À JOUR 2: Quelques nouvelles réponses ont été apportées - au moins certaines d’entre elles offrent de meilleures solutions.

Mise à jour 3: Le ptools gem has ajoute une méthode "qui" à la classe File.

73
Yannick Wurm

Véritable solution multiplateforme, fonctionne correctement sous 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

Cela n'utilise pas le reniflement de l'OS hôte et respecte $ PATHEXT, qui répertorie les extensions de fichier valides pour les exécutables sous Windows.

Décaler à which fonctionne sur de nombreux systèmes mais pas sur tous.

112
mislav

Utilisez find_executable méthode de mkmf qui est incluse dans stdlib.

require 'mkmf'

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

find_executable 'which-Ruby'
#=> nil
70
NARKOZ
def command?(name)
  `which #{name}`
  $?.success?
end

Initialement pris à partir de hub , qui utilisait type -t au lieu de which cependant (et qui a échoué pour zsh et bash pour moi).

16
blueyed

Utiliser MakeMakefile # find_executable0 avec la journalisation désactivée

Il y a déjà un certain nombre de bonnes réponses, mais voici ce que j'utilise:

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

Ceci utilise la méthode non documentée # find_executable0 invoquée par MakeMakefile # find_executable pour renvoyer le chemin sans encombrer la sortie standard. La méthode #which redirige également temporairement le fichier journal mkmf vers / dev/null afin d'éviter d'encombrer le répertoire de travail actuel avec "mkmf.log" ou similaire.

6
Todd A. Jacobs

Vous pouvez accéder aux variables d'environnement système avec le hachage ENV:

puts ENV['PATH']

Il retournera le PATH sur votre système. Donc, si vous voulez savoir si le programme nmap existe, vous pouvez faire ceci:

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

Ceci imprimera true si le fichier a été trouvé ou false sinon.

5
rogeriopvl

J'aimerais ajouter que which prend l'indicateur -s pour le mode silencieux, qui ne définit que l'indicateur de succès, ce qui supprime la nécessité de rediriger la sortie.

2
olleolleolle

J'ai ceci:

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

fonctionne pour les chemins complets ainsi que les commandes:

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

Il y avait un GEM appelé which_Ruby qui était un pur Ruby dont la mise en œuvre ..__ n'est plus disponible.

Cependant, j'ai trouvé cette implémentation alternative pure-Ruby .

2
Simone Carletti

Voici ce que j'utilise. Ceci est neutre (File::PATH_SEPARATOR est ":" sous Unix et ";" sous Windows), recherche uniquement les fichiers de programme réellement exécutables par l'utilisateur effectif du processus en cours et se termine dès que le programme est trouvé:

##
# 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
2
Arto Bendiken

Ceci est une version améliorée basée sur La réponse de @ mislav . Cela permettrait n'importe quel type d'entrée de chemin et suivrait rigoureusement la façon dont cmd.exe choisit le fichier à exécuter dans 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
2
konsolebox

Sur Linux j'utilise:

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

Malheureusement, which n’est pas une commande POSIX et se comporte donc différemment sous Mac, BSD, etc. (c’est-à-dire qu’il génère une erreur si la commande n’est pas trouvée). Peut-être que la solution idéale serait d'utiliser 

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

Mais cela échoue car Ruby semble ne pas être capable d'accéder aux fonctions intégrées. Mais command -v serait le moyen POSIX de le faire.

1
bwv549

Solution basée sur rogeriovl, mais fonction complète avec test d'exécution plutôt que test d'existence.

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

Ne fonctionnera que sous UNIX (Windows n'utilise pas les deux points comme séparateur)

1
lzap
#####################################################
# 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
0
Jeremiah

pour jruby, l’une des solutions dépendant de mkmf risque de ne pas fonctionner car elle a une extension C.

pour jruby, voici un moyen simple de vérifier si quelque chose est exécutable sur le chemin:

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

si l'exécutable n'est pas présent, une erreur d'exécution sera générée. Vous souhaiterez peut-être le faire dans un bloc try/catch dans votre utilisation réelle.

0
Alex Moore-Niemi

Ceci est un Tweak de la réponse de rogeriopvl, ce qui la rend multi-plateforme:

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
kolrie