"Welches in Ruby": Überprüfen, ob ein Programm in $ PATH von Ruby vorhanden ist


76

Meine Skripte stützen sich stark auf externe Programme und Skripte. Ich muss sicher sein, dass ein Programm vorhanden ist, das ich aufrufen muss. Manuell würde ich dies mit 'which' in der Kommandozeile überprüfen.

Gibt es ein Äquivalent zu File.exists?für Dinge in $PATH?

(Ja, ich denke ich könnte analysieren, %x[which scriptINeedToRun]aber das ist nicht super elegant.

Vielen Dank! Yannick


UPDATE: Hier ist die Lösung, die ich beibehalten habe:

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

UPDATE 2: Einige neue Antworten sind eingegangen - zumindest einige davon bieten bessere Lösungen.

Update 3: Das Gem ptools hat der File-Klasse eine "which" -Methode hinzugefügt .


Ich habe diese Methode gerade getestet, sie funktioniert nicht. Der Befehl whichbefehl in der Methode gibt entweder 1 zurück, wenn der Befehl commandnicht vorhanden ist, oder 0, wenn der Befehl commandvorhanden ist. Damit die Methode funktioniert, sollten Sie 127 durch 1 ersetzen
Robert Audi

Die Lösung funktioniert nur auf Unix-Systemen, auf denen der Befehl whichvorhanden ist. Dies schließt Windows und einige andere Systeme aus. Bitte denken Sie daran, dass Windows unter Ruby-Entwicklern immer noch stark genutzt wird. Siehe meine Lösung für einen echten plattformübergreifenden Befehl.
Mislav

3
Ihre Antwort zum Bearbeiten ist nicht sicher - kann mit Code wie "; rm-rf" injiziert werden.
lzap

1
Imho die Antwort von NARKOZ ist perfekt! find_executable
Awenkhh

1
Lösung ptoolsgem funktionierte perfekt für mich!
Arnlen

Antworten:


123

Echte plattformübergreifende Lösung, funktioniert unter Windows ordnungsgemäß:

# 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

Dies verwendet kein Host-Betriebssystem-Sniffing und respektiert $ PATHEXT, das gültige Dateierweiterungen für ausführbare Dateien unter Windows auflistet.

Shelling out whichfunktioniert auf vielen Systemen, aber nicht auf allen.


1
Hinweis: Die aktuelle Antwort behandelt auch keine (vernünftigen?) Randfälle, wenn ein absoluter oder relativer Pfad als cmd( /bin/shoder ..\foo) enthalten ist.

Warum !File.directory?(exe)statt File.file?(exe)?
user137369

80

Verwenden Sie die find_executableMethode, von mkmfder aus stdlib enthalten ist.

require 'mkmf'

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

find_executable 'which-ruby'
#=> nil

3
Der einzige kleine Nachteil unter Windows ist, dass standardmäßig die Liste der ausführbaren Erweiterungen aus der Box verwendet wird, in der Ihre Ruby-Kopie erstellt wurde, und nicht die lokale Liste. MakeMakefile::CONFIG["EXECUTABLE_EXTS"] = ENV['PATHEXT'].split(';').join(' ')sollte das beheben.
Matt

23
Durch das Aufrufen von mkmf werden Ihre Verzeichnisse mit mkmf.log-Dateien verschmutzt.
Maasha

6
Sie können den MakeMakefile-Logger patchen, um die Protokolldatei in das Ruby-Äquivalent zu schreiben, /dev/nullwenn Sie das Erstellen physischer Protokolldateien vermeiden möchten. In dieser Übersicht finden Sie ein Beispiel: gist.github.com/mnem/2540fece4ed9d3403b98
Mnem

6
Das Erfordernis mkmfist im Allgemeinen eine schlechte Idee und wird von Ruby-Entwicklern abgeraten (siehe bugs.ruby-lang.org/issues/12370#note-4 ) - es ist nur für die Verwendung in extconf.rb
fabi

3
Auch require 'mkmf'verpestet Ihren globalen Namensraum mit seinen Funktionen. In einem Skript ist es wahrscheinlich in Ordnung, in einer breiteren App jedoch eine schreckliche Übung.
Ejoubaud

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

Ursprünglich vom Hub genommen , der type -tstattdessen verwendet wurde which(und der für mich sowohl für zsh als auch für bash fehlgeschlagen ist).


4
Dies ist auf * nix-Plattformen weiter verbreitet, gibt jedoch nicht auf allen Plattformen einen Exit-Status ungleich Null zurück, wenn nichts gefunden wird. command -vist der Posix-Standard und auf Posix-Plattformen zuverlässiger.
Ry Biesemeyer

1
Warum suchst du nicht nach which #{name}.empty??
Mohsen

1
Denn unter SmartOS (und anderen Varianten von Illumnos) gibt 'welcher Befehl' die folgende Zeichenfolge zurück, wenn nichts gefunden wird: gibt > which foozurückno foo in /opt/local/bin /opt/local/sbin /usr/bin /usr/sbin
Konstantin Gredeskoul

6

Verwenden Sie MakeMakefile # find_executable0 mit deaktivierter Protokollierung

Es gibt bereits eine Reihe guter Antworten, aber ich verwende Folgendes:

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

Dies verwendet die undokumentierte Methode # find_executable0 , die von MakeMakefile # find_executable aufgerufen wird , um den Pfad zurückzugeben, ohne die Standardausgabe zu überladen . Die Methode #which leitet die mkmf-Protokolldatei auch vorübergehend nach / dev / null um, um zu verhindern, dass das aktuelle Arbeitsverzeichnis mit "mkmf.log" oder ähnlichem überfüllt ist.


1
Nachdem jemand diese Lösung bei der Arbeit verwendet hatte, stellte ich fest, dass es ein Problem gibt, bei dem mkmfes bei ffieinigen Methodendefinitionen (von denen ich annehme, dass beide Edelsteine ​​im Root-Namespace definiert sind) zu Konflikten mit gem-basierten Gems kommt. Dies schränkt den Nutzen der Lösung ein.
Neil Slater

Dies ist nicht threadsicher und verwendet eine interne API, die sich ändern kann.

5

Sie können mit dem ENV-Hash auf Systemumgebungsvariablen zugreifen:

puts ENV['PATH']

Der PATH wird auf Ihrem System zurückgegeben. Wenn Sie also wissen möchten, ob ein Programm nmapvorhanden ist, können Sie dies tun:

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

Dies wird gedruckt, truewenn eine Datei gefunden wurde oder falsenicht.


1
Sie sollten wahrscheinlich auch überprüfen, ob die Datei vom Benutzer ausführbar ist: File.exists? (...) und File.executable? (...). +1 auf jeden Fall.
liwp

Was ist mit der Erweiterung des Weges? Vielleicht ist es auch besser, File.join oder Pathname zu verwenden. Auch warum nicht welche verwenden? Es ist ein sehr gutes Werkzeug und macht seinen Job.
Tig

1
Ich zweite @kolrie; Dies ist keine plattformübergreifende. Siehe meine Lösung
Mislav

3

Hier ist was ich benutze. Dies ist plattformneutral ( File::PATH_SEPARATORist ":"auf Unix und ";"Windows), sieht nur für Programmdateien , die tatsächlich durch die effektiven Benutzer des aktuellen Prozesses ausgeführt werden kann , und endet, sobald das Programm gefunden wird:

##
# 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
Dies berücksichtigt nicht die Umgebungsvariable $ PATHEXT.
Mislav


2

Ich möchte hinzufügen, dass whichdas Flag -sfür den stillen Modus verwendet wird, wodurch nur das Erfolgsflag gesetzt wird, sodass die Ausgabe nicht mehr umgeleitet werden muss.


whichAkzeptiert auf meinem System kein -s-Flag, ist das irgendwo angegeben?
ComputerDruid

man whicherzählte mir von meinem System. Auf meiner Manpage heißt es jedoch: "Einige Shells enthalten möglicherweise einen integrierten Befehl, der diesem Dienstprogramm ähnlich oder identisch ist. Lesen Sie die integrierte Handbuchseite (1)." YMMV.
olleolleolle

2

Dies ist eine verbesserte Version, die auf der Antwort von @ mislav basiert . Dies würde jede Art von Pfadeingabe ermöglichen und folgt genau der cmd.exeAuswahl der Datei, die in Windows ausgeführt werden soll.

# 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 Sie entscheiden nicht, was unnötig ist.
konsolebox

2

Ich habe das:

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

funktioniert für vollständige Pfade sowie Befehle:

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

Unter Linux benutze ich:

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

Leider whichhandelt es sich nicht um einen POSIX-Befehl und verhält sich daher auf Mac, BSD usw. anders (dh es wird ein Fehler ausgegeben, wenn der Befehl nicht gefunden wird). Vielleicht wäre die ideale Lösung zu verwenden

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

Dies schlägt jedoch fehl, da Ruby anscheinend nicht in der Lage ist, auf integrierte Funktionen zuzugreifen. Wäre command -vaber der POSIX-Weg, dies zu tun.


2
Dies ist richtig. Sie müssen nur sh -c 'command -v #command', und Sie haben es. Ich habe versucht, Ihre Antwort hier zu diesem Zweck zu bearbeiten, aber sie wurde abgelehnt, weil ich anscheinend "Ihre Bedeutung geändert" habe.
Geoff Nixon

Unter Linux verwende ich: which #{command}existes = .size.> (0) Leider whichhandelt es sich nicht um einen POSIX-Befehl und verhält sich daher auf Mac, BSD usw. anders (dh es wird ein Fehler ausgegeben , wenn der Befehl nicht gefunden wird). Die ideale Lösung ist die Verwendung von sh -c 'command -v #{command}'.size.> (0) Dies sh -cist erforderlich, da Ruby sonst nicht auf integrierte Funktionen zugreifen kann. Aber command -vwäre der POSIX-Weg, dies zu tun.
Geoff Nixon

1

Lösung basierend auf Rogeriovl, aber vollständige Funktion mit Ausführungstest statt Existenztest.

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

Funktioniert nur unter UNIX (Windows verwendet keinen Doppelpunkt als Trennzeichen)


OK danke. Bitte beachten Sie jedoch, dass dies nur unter UNIX funktioniert. Gut mit dem Entfernen des Geschwätzes, aber die Notiz sollte dort bleiben.
lzap

0

Dies ist eine Optimierung der Antwort von Rogeriopvl, die es plattformübergreifend macht:

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

für jruby eine der Lösungen, die davon abhängen mkmf funktioniert möglicherweise keine , da sie eine C-Erweiterung hat.

Für jruby ist Folgendes eine einfache Möglichkeit, um zu überprüfen, ob auf dem Pfad etwas ausführbar ist:

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

Wenn die ausführbare Datei nicht vorhanden ist, wird ein Laufzeitfehler ausgelöst. Daher möchten Sie dies möglicherweise in einem Try / Catch-Block in Ihrer tatsächlichen Verwendung tun.


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

Nicht so elegant, aber es funktioniert :).

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

Warnung : Dies wird NICHT empfohlen, gefährlicher Rat!


1
Könnte lang sein, besser System ("welches" + c) dann verwenden.
Philant

2
anrufen cmdExists?('rm -rf ~'). Auch Ruby Konvention ist es, Methoden wiecmd_exists?
tig

Wirklich exzellenter Rat. Dies kann sogar Ihre Festplatte abwischen. NICHT EMPFOHLEN!
lzap
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.