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