"which in ruby": Checking if program exists in $PATH from ruby

RubyUnixPath

Ruby Problem Overview


my scripts rely heavily on external programs and scripts. I need to be sure that a program I need to call exists. Manually, I'd check this using 'which' in the commandline.

Is there an equivalent to File.exists? for things in $PATH?

(yes I guess I could parse %x[which scriptINeedToRun] but that's not super elegant.

Thanks! yannick


UPDATE: Here's the solution I retained:

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

UPDATE 2: A few new answers have come in - at least some of these offer better solutions.
Update 3: The ptools gem has adds a "which" method to the File class.

Ruby Solutions


Solution 1 - Ruby

True cross-platform solution, works properly on 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

This doesn't use host OS sniffing, and respects $PATHEXT which lists valid file extensions for executables on Windows.

Shelling out to which works on many systems but not all.

Solution 2 - Ruby

Use find_executable method from mkmf which is included to stdlib.

require 'mkmf'

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

find_executable 'which-ruby'
#=> nil

Solution 3 - Ruby

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

Initially taken from hub, which used type -t instead of which though (and which failed for both zsh and bash for me).

Solution 4 - Ruby

Use MakeMakefile#find_executable0 with Logging Disabled

There are a number of good answers already, but here's what I use:

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

This uses the undocumented #find_executable0 method invoked by MakeMakefile#find_executable to return the path without cluttering standard output. The #which method also temporarily redirects the mkmf logfile to /dev/null to prevent cluttering the current working directory with "mkmf.log" or similar.

Solution 5 - Ruby

You can access system environment variables with the ENV hash:

puts ENV['PATH']

It will return the PATH on your system. So if you want to know if program nmap exists, you can do this:

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

This will print true if file was found or false otherwise.

Solution 6 - Ruby

Here's what I'm using. This is platform neutral (File::PATH_SEPARATOR is ":" on Unix and ";" on Windows), only looks for program files that actually are executable by the effective user of the current process, and terminates as soon as the program is found:

##
# 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

Solution 7 - Ruby

I have this:

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

works for full paths as well as commands:

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

Solution 8 - Ruby

I'd like to add that which takes the flag -s for silent mode, which only sets the success flag, removing the need for redirecting the output.

Solution 9 - Ruby

This is an improved version based on @mislav's answer. This would allow any type of path input and strictly follows how cmd.exe chooses the file to execute in 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

Solution 10 - Ruby

Solution based on rogeriovl, but complete function with execution test rather than existence test.

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

Will work only for UNIX (Windows does not use colon as a separator)

Solution 11 - Ruby

On linux I use:

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

Unfortunately, which is not a POSIX command and so behaves differently on Mac, BSD, etc (i.e., throws an error if the command is not found). Maybe the ideal solution would be to use

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

But this fails because ruby seems to not be capable of accessing built-in functions. But command -v would be the POSIX way to do this.

Solution 12 - Ruby

This is a tweak of rogeriopvl's answer, making it cross platform:

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

Solution 13 - Ruby

for jruby, any of the solutions that depend on mkmf may not work, as it has a C extension.

for jruby, the following is an easy way to check if something is executable on the path:

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

if the executable isn't there, it will raise a runtime error, so you may want to do this in a try/catch block in your actual usage.

Solution 14 - Ruby

#####################################################
# 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

Solution 15 - Ruby

Not so much elegant but it works :).

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

Warning: This is NOT recommended, dangerous advice!

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionYannick WurmView Question on Stackoverflow
Solution 1 - RubymislavView Answer on Stackoverflow
Solution 2 - RubyNARKOZView Answer on Stackoverflow
Solution 3 - RubyblueyedView Answer on Stackoverflow
Solution 4 - RubyTodd A. JacobsView Answer on Stackoverflow
Solution 5 - RubyrogeriopvlView Answer on Stackoverflow
Solution 6 - RubyArto BendikenView Answer on Stackoverflow
Solution 7 - RubyingerView Answer on Stackoverflow
Solution 8 - RubyolleolleolleView Answer on Stackoverflow
Solution 9 - RubykonsoleboxView Answer on Stackoverflow
Solution 10 - RubylzapView Answer on Stackoverflow
Solution 11 - Rubybwv549View Answer on Stackoverflow
Solution 12 - RubykolrieView Answer on Stackoverflow
Solution 13 - RubyAlex Moore-NiemiView Answer on Stackoverflow
Solution 14 - RubyJeremiahView Answer on Stackoverflow
Solution 15 - RubybhupsView Answer on Stackoverflow