“Which in ruby”: Kiểm tra xem chương trình có tồn tại trong $ PATH từ ruby ​​không


76

các tập lệnh của tôi chủ yếu dựa vào các chương trình và tập lệnh bên ngoài. Tôi cần chắc chắn rằng có tồn tại một chương trình tôi cần gọi. Theo cách thủ công, tôi sẽ kiểm tra điều này bằng cách sử dụng 'which' trong dòng lệnh.

Có tương đương File.exists?với những thứ trong $PATHkhông?

(vâng, tôi đoán tôi có thể phân tích cú pháp %x[which scriptINeedToRun]nhưng điều đó không quá thanh lịch.

Cảm ơn! yannick


CẬP NHẬT: Đây là giải pháp tôi đã giữ lại:

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

CẬP NHẬT 2: Một số câu trả lời mới đã xuất hiện - ít nhất một số câu trả lời trong số này cung cấp các giải pháp tốt hơn.

Cập nhật 3: Ptools gem đã thêm một phương thức "which" vào lớp File.


Tôi vừa thử nghiệm phương pháp này, nó không hoạt động. Lệnh whichlệnh trong phương thức sẽ trả về 1 nếu lệnh commandkhông tồn tại hoặc 0 nếu lệnh commandtồn tại. Vì vậy, để thực hiện công việc phương pháp, bạn nên thay thế 127 bằng 1
Robert Audi

Giải pháp sẽ chỉ hoạt động trên các hệ thống unix có lệnh which. Điều này không bao gồm Windows và một số hệ thống khác. Hãy nhớ rằng Windows vẫn được sử dụng nhiều trong các nhà phát triển Ruby; xem giải pháp của tôi cho một lệnh đa nền tảng thực sự.
mislav

3
Câu trả lời chỉnh sửa của bạn không an toàn - có thể được chèn bằng mã như "; rm-rf".
lzap

1
imho câu trả lời từ NARKOZ là hoàn hảo! find_executable
awenkhh

1
Giải pháp ptoolsđá quý hoạt động hoàn hảo cho tôi!
Arnlen

Câu trả lời:


123

Giải pháp đa nền tảng thực sự, hoạt động bình thường trên 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

Điều này không sử dụng tính năng đánh hơi hệ điều hành máy chủ và tôn trọng $ PATHEXT, nơi liệt kê các phần mở rộng tệp hợp lệ cho các tệp thực thi trên Windows.

Bắn ra whichhoạt động trên nhiều hệ thống nhưng không phải tất cả.


1
Lưu ý: Câu trả lời hiện tại cũng không xử lý các trường hợp cạnh (hợp lý?) Khi một đường dẫn tuyệt đối hoặc tương đối được bao gồm dưới dạng cmd( /bin/shhoặc ..\foo).

Tại sao !File.directory?(exe)thay vì File.file?(exe)?
user137369

80

Sử dụng find_executablephương thức mkmfđược đưa vào stdlib.

require 'mkmf'

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

find_executable 'which-ruby'
#=> nil

3
Điểm nhỏ duy nhất trên Windows là điều này sẽ mặc định cho danh sách các tiện ích mở rộng thực thi từ hộp đã tạo bản sao ruby ​​của bạn chứ không phải danh sách cục bộ. MakeMakefile::CONFIG["EXECUTABLE_EXTS"] = ENV['PATHEXT'].split(';').join(' ')nên khắc phục điều đó.
Matt,

23
Gọi mkmf gây ô nhiễm thư mục của bạn với các tệp mkmf.log.
maasha

6
Bạn có thể vá trình ghi nhật ký MakeMakefile để ghi tệp nhật ký tương đương với ruby /dev/nullnếu bạn muốn tránh tạo tệp nhật ký vật lý. Xem ý chính này cho một ví dụ: gist.github.com/mnem/2540fece4ed9d3403b98
mnem

6
Yêu cầu mkmfnói chung là một ý tưởng tồi và được các nhà phát triển ruby ​​khuyên không nên (xem bug.ruby-lang.org/issues/12370#note-4 ) - nó chỉ được sử dụng trong extconf.rb
fabi

3
Cũng require 'mkmf'gây ô nhiễm không gian tên toàn cầu của bạn với các chức năng của nó. Nó có thể tốt trong một kịch bản nhưng thực tế tồi tệ trong một ứng dụng rộng hơn.
ejoubaud

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

Ban đầu được lấy từ trung tâm , được sử dụng type -tthay vì whichmặc dù (và không thành công cho cả zsh và bash đối với tôi).


4
có sẵn rộng rãi hơn trên các nền tảng * nix, nhưng không trả về trạng thái thoát khác 0 trên tất cả các nền tảng khi không tìm thấy gì. command -vlà tiêu chuẩn posix và đáng tin cậy hơn trên nền tảng posix.
Ry Biesemeyer

1
Tại sao bạn không kiểm tra which #{name}.empty??
Mohsen

1
Bởi vì trên SmartOS (và hương vị khác của Illumnos) mà lệnh 'trả về chuỗi sau khi nó không tìm thấy bất cứ điều gì: > which foolợi nhuậnno foo in /opt/local/bin /opt/local/sbin /usr/bin /usr/sbin
Konstantin Gredeskoul

6

Sử dụng MakeMakefile # find_executable0 với tính năng ghi nhật ký bị tắt

Đã có một số câu trả lời hay, nhưng đây là những gì tôi sử dụng:

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

Điều này sử dụng phương thức # find_executable0 không có giấy tờ được gọi bởi MakeMakefile # find_executable để trả về đường dẫn mà không làm lộn xộn đầu ra chuẩn. Phương thức #which cũng tạm thời chuyển hướng tệp nhật ký mkmf đến / dev / null để ngăn làm lộn xộn thư mục làm việc hiện tại với "mkmf.log" hoặc tương tự.


1
Tôi phát hiện ra, sau khi ai đó sử dụng giải pháp này tại nơi làm việc, có một vấn đề mkmfsẽ xảy ra xung đột với các ffigem dựa trên một số định nghĩa phương thức (mà tôi cho rằng cả hai gem đều xác định trong không gian tên gốc). Điều đó làm hạn chế tính hữu ích của giải pháp.
Neil Slater

Điều này không an toàn cho chuỗi và nó sử dụng một API nội bộ có thể thay đổi.

5

Bạn có thể truy cập các biến môi trường hệ thống bằng mã băm ENV:

puts ENV['PATH']

Nó sẽ trả về PATH trên hệ thống của bạn. Vì vậy, nếu bạn muốn biết chương trình nmapcó tồn tại hay không, bạn có thể làm như sau:

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

Điều này sẽ in truenếu tệp được tìm thấy hoặc falsenếu không.


1
bạn có thể cũng nên kiểm tra xem tệp có được người dùng thực thi không: File.exists? (...) và File.executable? (...). +1 trong mọi trường hợp.
liwp

Còn về con đường mở rộng? Có thể tốt hơn là sử dụng File.join hoặc Pathname. Ngoài ra tại sao không sử dụng cái nào? Nó là một công cụ rất tốt và nó thực hiện công việc của mình.
tig

1
Tôi thứ hai @kolrie; đây không phải là nền tảng chéo. Xem giải pháp của tôi
mislav

3

Đây là những gì tôi đang sử dụng. Đây là nền tảng trung lập ( File::PATH_SEPARATOR":"trên Unix và ";"trên Windows), chỉ trông cho các tập tin chương trình mà thực sự là thực thi bởi người sử dụng có hiệu quả các quá trình hiện tại, và chấm dứt ngay sau khi chương trình được tìm thấy:

##
# 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
Điều này không tuân theo biến môi trường $ PATHEXT.
mislav


2

Tôi muốn thêm rằng whichlấy cờ -scho chế độ im lặng, chỉ đặt cờ thành công, loại bỏ nhu cầu chuyển hướng đầu ra.


Trên hệ thống của tôi whichkhông chấp nhận cờ -s, điều đó có được chỉ định ở đâu đó không?
ComputerDruid

man whichđã nói với tôi về hệ thống của tôi. Tuy nhiên, trang người đàn ông của tôi nói rằng "Một số trình bao có thể cung cấp một nội trang mà lệnh tương tự hoặc giống với tiện ích này. Tham khảo trang hướng dẫn nội trang (1)." YMMV.
olleolleolle

2

Đây là phiên bản cải tiến dựa trên câu trả lời của @ mislav . Điều này sẽ cho phép bất kỳ loại đầu vào đường dẫn nào và tuân thủ nghiêm ngặt cách cmd.exechọn tệp để thực thi trong 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 Không phải bạn là người quyết định những gì không cần thiết.
konsolebox

2

Tôi có cái này:

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

hoạt động cho các đường dẫn đầy đủ cũng như các lệnh:

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

Trên linux tôi sử dụng:

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

Thật không may, whichkhông phải là một lệnh POSIX và do đó hoạt động khác nhau trên Mac, BSD, v.v. (tức là, tạo ra lỗi nếu không tìm thấy lệnh). Có lẽ giải pháp lý tưởng sẽ là sử dụng

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

Nhưng điều này không thành công vì ruby ​​dường như không có khả năng truy cập các chức năng được tích hợp sẵn. Nhưng command -vsẽ là cách POSIX để làm điều này.


2
Đúng rồi đó. Bạn chỉ cần làm sh -c 'command -v #command', và bạn đã có nó. Tôi đã thử chỉnh sửa câu trả lời của bạn ở đây để có hiệu lực đó, nhưng nó đã bị từ chối vì rõ ràng, tôi đang "thay đổi ý nghĩa của bạn".
Geoff Nixon

Trên linux, tôi sử dụng: which #{command}being = .size.> (0) Thật không may, whichkhông phải là lệnh POSIX và do đó hoạt động khác trên Mac, BSD, v.v. (tức là, sẽ xảy ra lỗi nếu không tìm thấy lệnh). Giải pháp lý tưởng là sử dụng sh -c 'command -v #{command}'.size.> (0) Điều sh -cnày là cần thiết vì nếu không ruby ​​sẽ không thể truy cập các hàm tích hợp. Nhưng command -vsẽ là cách POSIX để làm điều này.
Geoff Nixon

1

Giải pháp dựa trên rogeriovl, nhưng hoàn thành chức năng với kiểm tra thực thi hơn là kiểm tra tồn tại.

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

Sẽ chỉ hoạt động với UNIX (Windows không sử dụng dấu hai chấm làm dấu phân tách)


Được rồi cảm ơn. Nhưng xin lưu ý rằng điều này sẽ chỉ hoạt động cho UNIX. Tốt với việc loại bỏ rant, nhưng lưu ý nên ở đó.
lzap

0

Đây là một bản điều chỉnh câu trả lời của rogeriopvl, làm cho nó đa nền tảng:

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

đối với jruby, bất kỳ giải pháp nào phụ thuộc vào mkmfcó thể không hoạt động, vì nó có phần mở rộng C.

đối với jruby, sau đây là một cách dễ dàng để kiểm tra xem thứ gì đó có thể thực thi được trên đường dẫn hay không:

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

nếu tệp thực thi không có ở đó, nó sẽ gây ra lỗi thời gian chạy, vì vậy bạn có thể muốn thực hiện việc này trong một khối try / catch trong cách sử dụng thực tế của bạn.


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

Không quá thanh lịch nhưng nó hoạt động :).

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

Cảnh báo : Đây là lời khuyên KHÔNG được khuyến khích, nguy hiểm !


1
Có thể là dài, tốt hơn sử dụng hệ thống ("which" + c) sau đó.
thiện

2
cuộc gọi cmdExists?('rm -rf ~'). Cũng ước ruby là đặt tên các phương pháp nhưcmd_exists?
tig

Lời khuyên thực sự xuất sắc . Điều này thậm chí có thể xóa sạch hdd của bạn. KHÔNG ĐƯỢC KHUYẾN KHÍCH!
lzap
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.