| 
0
 | 
     1 #!/usr/bin/env python
 | 
| 
 | 
     2 # Copyright (c) 2002-2005 ActiveState Corp.
 | 
| 
 | 
     3 # See LICENSE.txt for license details.
 | 
| 
 | 
     4 # Author:
 | 
| 
 | 
     5 #   Trent Mick (TrentM@ActiveState.com)
 | 
| 
 | 
     6 # Home:
 | 
| 
 | 
     7 #   http://trentm.com/projects/which/
 | 
| 
 | 
     8 
 | 
| 
 | 
     9 r"""Find the full path to commands.
 | 
| 
 | 
    10 
 | 
| 
 | 
    11 which(command, path=None, verbose=0, exts=None)
 | 
| 
 | 
    12     Return the full path to the first match of the given command on the
 | 
| 
 | 
    13     path.
 | 
| 
 | 
    14 
 | 
| 
 | 
    15 whichall(command, path=None, verbose=0, exts=None)
 | 
| 
 | 
    16     Return a list of full paths to all matches of the given command on
 | 
| 
 | 
    17     the path.
 | 
| 
 | 
    18 
 | 
| 
 | 
    19 whichgen(command, path=None, verbose=0, exts=None)
 | 
| 
 | 
    20     Return a generator which will yield full paths to all matches of the
 | 
| 
 | 
    21     given command on the path.
 | 
| 
 | 
    22     
 | 
| 
 | 
    23 By default the PATH environment variable is searched (as well as, on
 | 
| 
 | 
    24 Windows, the AppPaths key in the registry), but a specific 'path' list
 | 
| 
 | 
    25 to search may be specified as well.  On Windows, the PATHEXT environment
 | 
| 
 | 
    26 variable is applied as appropriate.
 | 
| 
 | 
    27 
 | 
| 
 | 
    28 If "verbose" is true then a tuple of the form
 | 
| 
 | 
    29     (<fullpath>, <matched-where-description>)
 | 
| 
 | 
    30 is returned for each match. The latter element is a textual description
 | 
| 
 | 
    31 of where the match was found. For example:
 | 
| 
 | 
    32     from PATH element 0
 | 
| 
 | 
    33     from HKLM\SOFTWARE\...\perl.exe
 | 
| 
 | 
    34 """
 | 
| 
 | 
    35 
 | 
| 
 | 
    36 _cmdlnUsage = """
 | 
| 
 | 
    37     Show the full path of commands.
 | 
| 
 | 
    38 
 | 
| 
 | 
    39     Usage:
 | 
| 
 | 
    40         which [<options>...] [<command-name>...]
 | 
| 
 | 
    41 
 | 
| 
 | 
    42     Options:
 | 
| 
 | 
    43         -h, --help      Print this help and exit.
 | 
| 
 | 
    44         -V, --version   Print the version info and exit.
 | 
| 
 | 
    45 
 | 
| 
 | 
    46         -a, --all       Print *all* matching paths.
 | 
| 
 | 
    47         -v, --verbose   Print out how matches were located and
 | 
| 
 | 
    48                         show near misses on stderr.
 | 
| 
 | 
    49         -q, --quiet     Just print out matches. I.e., do not print out
 | 
| 
 | 
    50                         near misses.
 | 
| 
 | 
    51 
 | 
| 
 | 
    52         -p <altpath>, --path=<altpath>
 | 
| 
 | 
    53                         An alternative path (list of directories) may
 | 
| 
 | 
    54                         be specified for searching.
 | 
| 
 | 
    55         -e <exts>, --exts=<exts>
 | 
| 
 | 
    56                         Specify a list of extensions to consider instead
 | 
| 
 | 
    57                         of the usual list (';'-separate list, Windows
 | 
| 
 | 
    58                         only).
 | 
| 
 | 
    59 
 | 
| 
 | 
    60     Show the full path to the program that would be run for each given
 | 
| 
 | 
    61     command name, if any. Which, like GNU's which, returns the number of
 | 
| 
 | 
    62     failed arguments, or -1 when no <command-name> was given.
 | 
| 
 | 
    63 
 | 
| 
 | 
    64     Near misses include duplicates, non-regular files and (on Un*x)
 | 
| 
 | 
    65     files without executable access.
 | 
| 
 | 
    66 """
 | 
| 
 | 
    67 
 | 
| 
 | 
    68 __revision__ = "$Id: which.py 430 2005-08-20 03:11:58Z trentm $"
 | 
| 
 | 
    69 __version_info__ = (1, 1, 0)
 | 
| 
 | 
    70 __version__ = '.'.join(map(str, __version_info__))
 | 
| 
 | 
    71 
 | 
| 
 | 
    72 import os
 | 
| 
 | 
    73 import sys
 | 
| 
 | 
    74 import getopt
 | 
| 
 | 
    75 import stat
 | 
| 
 | 
    76 
 | 
| 
 | 
    77 
 | 
| 
 | 
    78 #---- exceptions
 | 
| 
 | 
    79 
 | 
| 
 | 
    80 class WhichError(Exception):
 | 
| 
 | 
    81     pass
 | 
| 
 | 
    82 
 | 
| 
 | 
    83 
 | 
| 
 | 
    84 
 | 
| 
 | 
    85 #---- internal support stuff
 | 
| 
 | 
    86 
 | 
| 
 | 
    87 def _getRegisteredExecutable(exeName):
 | 
| 
 | 
    88     """Windows allow application paths to be registered in the registry."""
 | 
| 
 | 
    89     registered = None
 | 
| 
 | 
    90     if sys.platform.startswith('win'):
 | 
| 
 | 
    91         if os.path.splitext(exeName)[1].lower() != '.exe':
 | 
| 
 | 
    92             exeName += '.exe'
 | 
| 
 | 
    93         import _winreg
 | 
| 
 | 
    94         try:
 | 
| 
 | 
    95             key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +\
 | 
| 
 | 
    96                   exeName
 | 
| 
 | 
    97             value = _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE, key)
 | 
| 
 | 
    98             registered = (value, "from HKLM\\"+key)
 | 
| 
 | 
    99         except _winreg.error:
 | 
| 
 | 
   100             pass
 | 
| 
 | 
   101         if registered and not os.path.exists(registered[0]):
 | 
| 
 | 
   102             registered = None
 | 
| 
 | 
   103     return registered
 | 
| 
 | 
   104 
 | 
| 
 | 
   105 def _samefile(fname1, fname2):
 | 
| 
 | 
   106     if sys.platform.startswith('win'):
 | 
| 
 | 
   107         return ( os.path.normpath(os.path.normcase(fname1)) ==\
 | 
| 
 | 
   108             os.path.normpath(os.path.normcase(fname2)) )
 | 
| 
 | 
   109     else:
 | 
| 
 | 
   110         return os.path.samefile(fname1, fname2)
 | 
| 
 | 
   111 
 | 
| 
 | 
   112 def _cull(potential, matches, verbose=0):
 | 
| 
 | 
   113     """Cull inappropriate matches. Possible reasons:
 | 
| 
 | 
   114         - a duplicate of a previous match
 | 
| 
 | 
   115         - not a disk file
 | 
| 
 | 
   116         - not executable (non-Windows)
 | 
| 
 | 
   117     If 'potential' is approved it is returned and added to 'matches'.
 | 
| 
 | 
   118     Otherwise, None is returned.
 | 
| 
 | 
   119     """
 | 
| 
 | 
   120     for match in matches:  # don't yield duplicates
 | 
| 
 | 
   121         if _samefile(potential[0], match[0]):
 | 
| 
 | 
   122             if verbose:
 | 
| 
 | 
   123                 sys.stderr.write("duplicate: %s (%s)\n" % potential)
 | 
| 
 | 
   124             return None
 | 
| 
 | 
   125     else:
 | 
| 
 | 
   126         if not stat.S_ISREG(os.stat(potential[0]).st_mode):
 | 
| 
 | 
   127             if verbose:
 | 
| 
 | 
   128                 sys.stderr.write("not a regular file: %s (%s)\n" % potential)
 | 
| 
 | 
   129         elif not os.access(potential[0], os.X_OK):
 | 
| 
 | 
   130             if verbose:
 | 
| 
 | 
   131                 sys.stderr.write("no executable access: %s (%s)\n"\
 | 
| 
 | 
   132                                  % potential)
 | 
| 
 | 
   133         else:
 | 
| 
 | 
   134             matches.append(potential)
 | 
| 
 | 
   135             return potential
 | 
| 
 | 
   136 
 | 
| 
 | 
   137         
 | 
| 
 | 
   138 #---- module API
 | 
| 
 | 
   139 
 | 
| 
 | 
   140 def whichgen(command, path=None, verbose=0, exts=None):
 | 
| 
 | 
   141     """Return a generator of full paths to the given command.
 | 
| 
 | 
   142     
 | 
| 
 | 
   143     "command" is a the name of the executable to search for.
 | 
| 
 | 
   144     "path" is an optional alternate path list to search. The default it
 | 
| 
 | 
   145         to use the PATH environment variable.
 | 
| 
 | 
   146     "verbose", if true, will cause a 2-tuple to be returned for each
 | 
| 
 | 
   147         match. The second element is a textual description of where the
 | 
| 
 | 
   148         match was found.
 | 
| 
 | 
   149     "exts" optionally allows one to specify a list of extensions to use
 | 
| 
 | 
   150         instead of the standard list for this system. This can
 | 
| 
 | 
   151         effectively be used as an optimization to, for example, avoid
 | 
| 
 | 
   152         stat's of "foo.vbs" when searching for "foo" and you know it is
 | 
| 
 | 
   153         not a VisualBasic script but ".vbs" is on PATHEXT. This option
 | 
| 
 | 
   154         is only supported on Windows.
 | 
| 
 | 
   155 
 | 
| 
 | 
   156     This method returns a generator which yields either full paths to
 | 
| 
 | 
   157     the given command or, if verbose, tuples of the form (<path to
 | 
| 
 | 
   158     command>, <where path found>).
 | 
| 
 | 
   159     """
 | 
| 
 | 
   160     matches = []
 | 
| 
 | 
   161     if path is None:
 | 
| 
 | 
   162         usingGivenPath = 0
 | 
| 
 | 
   163         path = os.environ.get("PATH", "").split(os.pathsep)
 | 
| 
 | 
   164         if sys.platform.startswith("win"):
 | 
| 
 | 
   165             path.insert(0, os.curdir)  # implied by Windows shell
 | 
| 
 | 
   166     else:
 | 
| 
 | 
   167         usingGivenPath = 1
 | 
| 
 | 
   168 
 | 
| 
 | 
   169     # Windows has the concept of a list of extensions (PATHEXT env var).
 | 
| 
 | 
   170     if sys.platform.startswith("win"):
 | 
| 
 | 
   171         if exts is None:
 | 
| 
 | 
   172             exts = os.environ.get("PATHEXT", "").split(os.pathsep)
 | 
| 
 | 
   173             # If '.exe' is not in exts then obviously this is Win9x and
 | 
| 
 | 
   174             # or a bogus PATHEXT, then use a reasonable default.
 | 
| 
 | 
   175             for ext in exts:
 | 
| 
 | 
   176                 if ext.lower() == ".exe":
 | 
| 
 | 
   177                     break
 | 
| 
 | 
   178             else:
 | 
| 
 | 
   179                 exts = ['.COM', '.EXE', '.BAT']
 | 
| 
 | 
   180         elif not isinstance(exts, list):
 | 
| 
 | 
   181             raise TypeError("'exts' argument must be a list or None")
 | 
| 
 | 
   182     else:
 | 
| 
 | 
   183         if exts is not None:
 | 
| 
 | 
   184             raise WhichError("'exts' argument is not supported on "\
 | 
| 
 | 
   185                              "platform '%s'" % sys.platform)
 | 
| 
 | 
   186         exts = []
 | 
| 
 | 
   187 
 | 
| 
 | 
   188     # File name cannot have path separators because PATH lookup does not
 | 
| 
 | 
   189     # work that way.
 | 
| 
 | 
   190     if os.sep in command or os.altsep and os.altsep in command:
 | 
| 
 | 
   191         pass
 | 
| 
 | 
   192     else:
 | 
| 
 | 
   193         for i in range(len(path)):
 | 
| 
 | 
   194             dirName = path[i]
 | 
| 
 | 
   195             # On windows the dirName *could* be quoted, drop the quotes
 | 
| 
 | 
   196             if sys.platform.startswith("win") and len(dirName) >= 2\
 | 
| 
 | 
   197                and dirName[0] == '"' and dirName[-1] == '"':
 | 
| 
 | 
   198                 dirName = dirName[1:-1]
 | 
| 
 | 
   199             for ext in ['']+exts:
 | 
| 
 | 
   200                 absName = os.path.abspath(
 | 
| 
 | 
   201                     os.path.normpath(os.path.join(dirName, command+ext)))
 | 
| 
 | 
   202                 if os.path.isfile(absName):
 | 
| 
 | 
   203                     if usingGivenPath:
 | 
| 
 | 
   204                         fromWhere = "from given path element %d" % i
 | 
| 
 | 
   205                     elif not sys.platform.startswith("win"):
 | 
| 
 | 
   206                         fromWhere = "from PATH element %d" % i
 | 
| 
 | 
   207                     elif i == 0:
 | 
| 
 | 
   208                         fromWhere = "from current directory"
 | 
| 
 | 
   209                     else:
 | 
| 
 | 
   210                         fromWhere = "from PATH element %d" % (i-1)
 | 
| 
 | 
   211                     match = _cull((absName, fromWhere), matches, verbose)
 | 
| 
 | 
   212                     if match:
 | 
| 
 | 
   213                         if verbose:
 | 
| 
 | 
   214                             yield match
 | 
| 
 | 
   215                         else:
 | 
| 
 | 
   216                             yield match[0]
 | 
| 
 | 
   217         match = _getRegisteredExecutable(command)
 | 
| 
 | 
   218         if match is not None:
 | 
| 
 | 
   219             match = _cull(match, matches, verbose)
 | 
| 
 | 
   220             if match:
 | 
| 
 | 
   221                 if verbose:
 | 
| 
 | 
   222                     yield match
 | 
| 
 | 
   223                 else:
 | 
| 
 | 
   224                     yield match[0]
 | 
| 
 | 
   225 
 | 
| 
 | 
   226 
 | 
| 
 | 
   227 def which(command, path=None, verbose=0, exts=None):
 | 
| 
 | 
   228     """Return the full path to the first match of the given command on
 | 
| 
 | 
   229     the path.
 | 
| 
 | 
   230     
 | 
| 
 | 
   231     "command" is a the name of the executable to search for.
 | 
| 
 | 
   232     "path" is an optional alternate path list to search. The default it
 | 
| 
 | 
   233         to use the PATH environment variable.
 | 
| 
 | 
   234     "verbose", if true, will cause a 2-tuple to be returned. The second
 | 
| 
 | 
   235         element is a textual description of where the match was found.
 | 
| 
 | 
   236     "exts" optionally allows one to specify a list of extensions to use
 | 
| 
 | 
   237         instead of the standard list for this system. This can
 | 
| 
 | 
   238         effectively be used as an optimization to, for example, avoid
 | 
| 
 | 
   239         stat's of "foo.vbs" when searching for "foo" and you know it is
 | 
| 
 | 
   240         not a VisualBasic script but ".vbs" is on PATHEXT. This option
 | 
| 
 | 
   241         is only supported on Windows.
 | 
| 
 | 
   242 
 | 
| 
 | 
   243     If no match is found for the command, a WhichError is raised.
 | 
| 
 | 
   244     """
 | 
| 
 | 
   245     try:
 | 
| 
 | 
   246         match = whichgen(command, path, verbose, exts).next()
 | 
| 
 | 
   247     except StopIteration:
 | 
| 
 | 
   248         raise WhichError("Could not find '%s' on the path." % command)
 | 
| 
 | 
   249     return match
 | 
| 
 | 
   250 
 | 
| 
 | 
   251 
 | 
| 
 | 
   252 def whichall(command, path=None, verbose=0, exts=None):
 | 
| 
 | 
   253     """Return a list of full paths to all matches of the given command
 | 
| 
 | 
   254     on the path.  
 | 
| 
 | 
   255 
 | 
| 
 | 
   256     "command" is a the name of the executable to search for.
 | 
| 
 | 
   257     "path" is an optional alternate path list to search. The default it
 | 
| 
 | 
   258         to use the PATH environment variable.
 | 
| 
 | 
   259     "verbose", if true, will cause a 2-tuple to be returned for each
 | 
| 
 | 
   260         match. The second element is a textual description of where the
 | 
| 
 | 
   261         match was found.
 | 
| 
 | 
   262     "exts" optionally allows one to specify a list of extensions to use
 | 
| 
 | 
   263         instead of the standard list for this system. This can
 | 
| 
 | 
   264         effectively be used as an optimization to, for example, avoid
 | 
| 
 | 
   265         stat's of "foo.vbs" when searching for "foo" and you know it is
 | 
| 
 | 
   266         not a VisualBasic script but ".vbs" is on PATHEXT. This option
 | 
| 
 | 
   267         is only supported on Windows.
 | 
| 
 | 
   268     """
 | 
| 
 | 
   269     return list( whichgen(command, path, verbose, exts) )
 | 
| 
 | 
   270 
 | 
| 
 | 
   271 
 | 
| 
 | 
   272 
 | 
| 
 | 
   273 #---- mainline
 | 
| 
 | 
   274 
 | 
| 
 | 
   275 def main(argv):
 | 
| 
 | 
   276     all = 0
 | 
| 
 | 
   277     verbose = 0
 | 
| 
 | 
   278     altpath = None
 | 
| 
 | 
   279     exts = None
 | 
| 
 | 
   280     try:
 | 
| 
 | 
   281         optlist, args = getopt.getopt(argv[1:], 'haVvqp:e:',
 | 
| 
 | 
   282             ['help', 'all', 'version', 'verbose', 'quiet', 'path=', 'exts='])
 | 
| 
 | 
   283     except getopt.GetoptError, msg:
 | 
| 
 | 
   284         sys.stderr.write("which: error: %s. Your invocation was: %s\n"\
 | 
| 
 | 
   285                          % (msg, argv))
 | 
| 
 | 
   286         sys.stderr.write("Try 'which --help'.\n")
 | 
| 
 | 
   287         return 1
 | 
| 
 | 
   288     for opt, optarg in optlist:
 | 
| 
 | 
   289         if opt in ('-h', '--help'):
 | 
| 
 | 
   290             print _cmdlnUsage
 | 
| 
 | 
   291             return 0
 | 
| 
 | 
   292         elif opt in ('-V', '--version'):
 | 
| 
 | 
   293             print "which %s" % __version__
 | 
| 
 | 
   294             return 0
 | 
| 
 | 
   295         elif opt in ('-a', '--all'):
 | 
| 
 | 
   296             all = 1
 | 
| 
 | 
   297         elif opt in ('-v', '--verbose'):
 | 
| 
 | 
   298             verbose = 1
 | 
| 
 | 
   299         elif opt in ('-q', '--quiet'):
 | 
| 
 | 
   300             verbose = 0
 | 
| 
 | 
   301         elif opt in ('-p', '--path'):
 | 
| 
 | 
   302             if optarg:
 | 
| 
 | 
   303                 altpath = optarg.split(os.pathsep)
 | 
| 
 | 
   304             else:
 | 
| 
 | 
   305                 altpath = []
 | 
| 
 | 
   306         elif opt in ('-e', '--exts'):
 | 
| 
 | 
   307             if optarg:
 | 
| 
 | 
   308                 exts = optarg.split(os.pathsep)
 | 
| 
 | 
   309             else:
 | 
| 
 | 
   310                 exts = []
 | 
| 
 | 
   311 
 | 
| 
 | 
   312     if len(args) == 0:
 | 
| 
 | 
   313         return -1
 | 
| 
 | 
   314 
 | 
| 
 | 
   315     failures = 0
 | 
| 
 | 
   316     for arg in args:
 | 
| 
 | 
   317         #print "debug: search for %r" % arg
 | 
| 
 | 
   318         nmatches = 0
 | 
| 
 | 
   319         for match in whichgen(arg, path=altpath, verbose=verbose, exts=exts):
 | 
| 
 | 
   320             if verbose:
 | 
| 
 | 
   321                 print "%s (%s)" % match
 | 
| 
 | 
   322             else:
 | 
| 
 | 
   323                 print match
 | 
| 
 | 
   324             nmatches += 1
 | 
| 
 | 
   325             if not all:
 | 
| 
 | 
   326                 break
 | 
| 
 | 
   327         if not nmatches:
 | 
| 
 | 
   328             failures += 1
 | 
| 
 | 
   329     return failures
 | 
| 
 | 
   330 
 | 
| 
 | 
   331 
 | 
| 
 | 
   332 if __name__ == "__main__":
 | 
| 
 | 
   333     sys.exit( main(sys.argv) )
 | 
| 
 | 
   334 
 | 
| 
 | 
   335 
 |