BASIS  r3148
which.py
Go to the documentation of this file.
00001 #! /usr/bin/env python
00002 
00003 # Copyright (c) 2002-2007 ActiveState Software Inc.
00004 #
00005 # Permission is hereby granted, free of charge, to any person obtaining a
00006 # copy of this software and associated documentation files (the
00007 # "Software"), to deal in the Software without restriction, including
00008 # without limitation the rights to use, copy, modify, merge, publish,
00009 # distribute, sublicense, and/or sell copies of the Software, and to
00010 # permit persons to whom the Software is furnished to do so, subject to
00011 # the following conditions:
00012 #
00013 # The above copyright notice and this permission notice shall be included
00014 # in all copies or substantial portions of the Software.
00015 #
00016 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
00017 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00018 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
00019 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
00020 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
00021 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
00022 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00023 #
00024 # Author:
00025 #   Trent Mick (TrentM@ActiveState.com)
00026 # Home:
00027 #   http://trentm.com/projects/which/
00028 
00029 r"""Find the full path to commands.
00030 
00031 which(command, path=None, verbose=0, exts=None)
00032     Return the full path to the first match of the given command on the
00033     path.
00034 
00035 whichall(command, path=None, verbose=0, exts=None)
00036     Return a list of full paths to all matches of the given command on
00037     the path.
00038 
00039 whichgen(command, path=None, verbose=0, exts=None)
00040     Return a generator which will yield full paths to all matches of the
00041     given command on the path.
00042     
00043 By default the PATH environment variable is searched (as well as, on
00044 Windows, the AppPaths key in the registry), but a specific 'path' list
00045 to search may be specified as well.  On Windows, the PATHEXT environment
00046 variable is applied as appropriate.
00047 
00048 If "verbose" is true then a tuple of the form
00049     (<fullpath>, <matched-where-description>)
00050 is returned for each match. The latter element is a textual description
00051 of where the match was found. For example:
00052     from PATH element 0
00053     from HKLM\SOFTWARE\...\perl.exe
00054 """
00055 
00056 _cmdlnUsage = """
00057     Show the full path of commands.
00058 
00059     Usage:
00060         which [<options>...] [<command-name>...]
00061 
00062     Options:
00063         -h, --help      Print this help and exit.
00064         -V, --version   Print the version info and exit.
00065 
00066         -a, --all       Print *all* matching paths.
00067         -v, --verbose   Print out how matches were located and
00068                         show near misses on stderr.
00069         -q, --quiet     Just print out matches. I.e., do not print out
00070                         near misses.
00071 
00072         -p <altpath>, --path=<altpath>
00073                         An alternative path (list of directories) may
00074                         be specified for searching.
00075         -e <exts>, --exts=<exts>
00076                         Specify a list of extensions to consider instead
00077                         of the usual list (';'-separate list, Windows
00078                         only).
00079 
00080     Show the full path to the program that would be run for each given
00081     command name, if any. Which, like GNU's which, returns the number of
00082     failed arguments, or -1 when no <command-name> was given.
00083 
00084     Near misses include duplicates, non-regular files and (on Un*x)
00085     files without executable access.
00086 """
00087 
00088 __revision__ = "Id: which.py 1448 2007-02-28 19:13:06Z trentm"
00089 __version_info__ = (1, 1, 3)
00090 __version__ = '.'.join(map(str, __version_info__))
00091 __all__ = ["which", "whichall", "whichgen", "WhichError"]
00092 
00093 import os
00094 import sys
00095 import getopt
00096 import stat
00097 
00098 
00099 #---- exceptions
00100 
00101 class WhichError(Exception):
00102     pass
00103 
00104 
00105 
00106 #---- internal support stuff
00107 
00108 def _getRegisteredExecutable(exeName):
00109     """Windows allow application paths to be registered in the registry."""
00110     registered = None
00111     if sys.platform.startswith('win'):
00112         if os.path.splitext(exeName)[1].lower() != '.exe':
00113             exeName += '.exe'
00114         import _winreg
00115         try:
00116             key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +\
00117                   exeName
00118             value = _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE, key)
00119             registered = (value, "from HKLM\\"+key)
00120         except _winreg.error:
00121             pass
00122         if registered and not os.path.exists(registered[0]):
00123             registered = None
00124     return registered
00125 
00126 def _samefile(fname1, fname2):
00127     if sys.platform.startswith('win'):
00128         return ( os.path.normpath(os.path.normcase(fname1)) ==\
00129             os.path.normpath(os.path.normcase(fname2)) )
00130     else:
00131         return os.path.samefile(fname1, fname2)
00132 
00133 def _cull(potential, matches, verbose=0):
00134     """Cull inappropriate matches. Possible reasons:
00135         - a duplicate of a previous match
00136         - not a disk file
00137         - not executable (non-Windows)
00138     If 'potential' is approved it is returned and added to 'matches'.
00139     Otherwise, None is returned.
00140     """
00141     for match in matches:  # don't yield duplicates
00142         if _samefile(potential[0], match[0]):
00143             if verbose:
00144                 sys.stderr.write("duplicate: %s (%s)\n" % potential)
00145             return None
00146     else:
00147         if not stat.S_ISREG(os.stat(potential[0]).st_mode):
00148             if verbose:
00149                 sys.stderr.write("not a regular file: %s (%s)\n" % potential)
00150         elif sys.platform != "win32" \
00151              and not os.access(potential[0], os.X_OK):
00152             if verbose:
00153                 sys.stderr.write("no executable access: %s (%s)\n"\
00154                                  % potential)
00155         else:
00156             matches.append(potential)
00157             return potential
00158 
00159         
00160 #---- module API
00161 
00162 def whichgen(command, path=None, verbose=0, exts=None):
00163     """Return a generator of full paths to the given command.
00164     
00165     "command" is a the name of the executable to search for.
00166     "path" is an optional alternate path list to search. The default it
00167         to use the PATH environment variable.
00168     "verbose", if true, will cause a 2-tuple to be returned for each
00169         match. The second element is a textual description of where the
00170         match was found.
00171     "exts" optionally allows one to specify a list of extensions to use
00172         instead of the standard list for this system. This can
00173         effectively be used as an optimization to, for example, avoid
00174         stat's of "foo.vbs" when searching for "foo" and you know it is
00175         not a VisualBasic script but ".vbs" is on PATHEXT. This option
00176         is only supported on Windows.
00177 
00178     This method returns a generator which yields either full paths to
00179     the given command or, if verbose, tuples of the form (<path to
00180     command>, <where path found>).
00181     """
00182     matches = []
00183     if path is None:
00184         usingGivenPath = 0
00185         path = os.environ.get("PATH", "").split(os.pathsep)
00186         if sys.platform.startswith("win"):
00187             path.insert(0, os.curdir)  # implied by Windows shell
00188     else:
00189         usingGivenPath = 1
00190 
00191     # Windows has the concept of a list of extensions (PATHEXT env var).
00192     if sys.platform.startswith("win"):
00193         if exts is None:
00194             exts = os.environ.get("PATHEXT", "").split(os.pathsep)
00195             # If '.exe' is not in exts then obviously this is Win9x and
00196             # or a bogus PATHEXT, then use a reasonable default.
00197             for ext in exts:
00198                 if ext.lower() == ".exe":
00199                     break
00200             else:
00201                 exts = ['.CMD', '.COM', '.EXE', '.BAT']
00202         elif not isinstance(exts, list):
00203             raise TypeError("'exts' argument must be a list or None")
00204     else:
00205         if exts is not None:
00206             raise WhichError("'exts' argument is not supported on "\
00207                              "platform '%s'" % sys.platform)
00208         exts = []
00209 
00210     # File name cannot have path separators because PATH lookup does not
00211     # work that way.
00212     if os.sep in command or os.altsep and os.altsep in command:
00213         if os.path.exists(command):
00214             match = _cull((command, "explicit path given"), matches, verbose)
00215             if match:
00216                 if verbose:
00217                     yield match
00218                 else:
00219                     yield match[0]
00220     else:
00221         for i in range(len(path)):
00222             dirName = path[i]
00223             # On windows the dirName *could* be quoted, drop the quotes
00224             if sys.platform.startswith("win") and len(dirName) >= 2\
00225                and dirName[0] == '"' and dirName[-1] == '"':
00226                 dirName = dirName[1:-1]
00227             for ext in ['']+exts:
00228                 absName = os.path.abspath(
00229                     os.path.normpath(os.path.join(dirName, command+ext)))
00230                 if os.path.isfile(absName):
00231                     if usingGivenPath:
00232                         fromWhere = "from given path element %d" % i
00233                     elif not sys.platform.startswith("win"):
00234                         fromWhere = "from PATH element %d" % i
00235                     elif i == 0:
00236                         fromWhere = "from current directory"
00237                     else:
00238                         fromWhere = "from PATH element %d" % (i-1)
00239                     match = _cull((absName, fromWhere), matches, verbose)
00240                     if match:
00241                         if verbose:
00242                             yield match
00243                         else:
00244                             yield match[0]
00245         match = _getRegisteredExecutable(command)
00246         if match is not None:
00247             match = _cull(match, matches, verbose)
00248             if match:
00249                 if verbose:
00250                     yield match
00251                 else:
00252                     yield match[0]
00253 
00254 
00255 def which(command, path=None, verbose=0, exts=None):
00256     """Return the full path to the first match of the given command on
00257     the path.
00258     
00259     "command" is a the name of the executable to search for.
00260     "path" is an optional alternate path list to search. The default it
00261         to use the PATH environment variable.
00262     "verbose", if true, will cause a 2-tuple to be returned. The second
00263         element is a textual description of where the match was found.
00264     "exts" optionally allows one to specify a list of extensions to use
00265         instead of the standard list for this system. This can
00266         effectively be used as an optimization to, for example, avoid
00267         stat's of "foo.vbs" when searching for "foo" and you know it is
00268         not a VisualBasic script but ".vbs" is on PATHEXT. This option
00269         is only supported on Windows.
00270 
00271     If no match is found for the command, a WhichError is raised.
00272     """
00273     try:
00274         match = whichgen(command, path, verbose, exts).next()
00275     except StopIteration:
00276         raise WhichError("Could not find '%s' on the path." % command)
00277     return match
00278 
00279 
00280 def whichall(command, path=None, verbose=0, exts=None):
00281     """Return a list of full paths to all matches of the given command
00282     on the path.  
00283 
00284     "command" is a the name of the executable to search for.
00285     "path" is an optional alternate path list to search. The default it
00286         to use the PATH environment variable.
00287     "verbose", if true, will cause a 2-tuple to be returned for each
00288         match. The second element is a textual description of where the
00289         match was found.
00290     "exts" optionally allows one to specify a list of extensions to use
00291         instead of the standard list for this system. This can
00292         effectively be used as an optimization to, for example, avoid
00293         stat's of "foo.vbs" when searching for "foo" and you know it is
00294         not a VisualBasic script but ".vbs" is on PATHEXT. This option
00295         is only supported on Windows.
00296     """
00297     return list( whichgen(command, path, verbose, exts) )
00298 
00299 
00300 
00301 #---- mainline
00302 
00303 def main(argv):
00304     all = 0
00305     verbose = 0
00306     altpath = None
00307     exts = None
00308     try:
00309         optlist, args = getopt.getopt(argv[1:], 'haVvqp:e:',
00310             ['help', 'all', 'version', 'verbose', 'quiet', 'path=', 'exts='])
00311     except getopt.GetoptError, msg:
00312         sys.stderr.write("which: error: %s. Your invocation was: %s\n"\
00313                          % (msg, argv))
00314         sys.stderr.write("Try 'which --help'.\n")
00315         return 1
00316     for opt, optarg in optlist:
00317         if opt in ('-h', '--help'):
00318             print _cmdlnUsage
00319             return 0
00320         elif opt in ('-V', '--version'):
00321             print "which %s" % __version__
00322             return 0
00323         elif opt in ('-a', '--all'):
00324             all = 1
00325         elif opt in ('-v', '--verbose'):
00326             verbose = 1
00327         elif opt in ('-q', '--quiet'):
00328             verbose = 0
00329         elif opt in ('-p', '--path'):
00330             if optarg:
00331                 altpath = optarg.split(os.pathsep)
00332             else:
00333                 altpath = []
00334         elif opt in ('-e', '--exts'):
00335             if optarg:
00336                 exts = optarg.split(os.pathsep)
00337             else:
00338                 exts = []
00339 
00340     if len(args) == 0:
00341         return -1
00342 
00343     failures = 0
00344     for arg in args:
00345         #print "debug: search for %r" % arg
00346         nmatches = 0
00347         for match in whichgen(arg, path=altpath, verbose=verbose, exts=exts):
00348             if verbose:
00349                 print "%s (%s)" % match
00350             else:
00351                 print match
00352             nmatches += 1
00353             if not all:
00354                 break
00355         if not nmatches:
00356             failures += 1
00357     return failures
00358 
00359 
00360 if __name__ == "__main__":
00361     sys.exit( main(sys.argv) )
00362 
00363