“which in ruby”: Checking if program exists in $PATH from ruby

后端 未结 16 1042
萌比男神i
萌比男神i 2020-12-07 12:15

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 commandlin

相关标签:
16条回答
  • 2020-12-07 12:33

    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.

    0 讨论(0)
  • 2020-12-07 12:34

    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
    
    0 讨论(0)
  • 2020-12-07 12:34

    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)

    0 讨论(0)
  • 2020-12-07 12:35

    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
    
    0 讨论(0)
  • 2020-12-07 12:38

    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
    
    0 讨论(0)
  • 2020-12-07 12:38

    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.

    0 讨论(0)
提交回复
热议问题