123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- #!/usr/bin/env python
- # Copyright (c) 2002-2005 ActiveState Corp.
- # See LICENSE.txt for license details.
- # Author:
- # Trent Mick (TrentM@ActiveState.com)
- # Home:
- # http://trentm.com/projects/which/
- r"""Find the full path to commands.
- which(command, path=None, verbose=0, exts=None)
- Return the full path to the first match of the given command on the
- path.
- whichall(command, path=None, verbose=0, exts=None)
- Return a list of full paths to all matches of the given command on
- the path.
- whichgen(command, path=None, verbose=0, exts=None)
- Return a generator which will yield full paths to all matches of the
- given command on the path.
-
- By default the PATH environment variable is searched (as well as, on
- Windows, the AppPaths key in the registry), but a specific 'path' list
- to search may be specified as well. On Windows, the PATHEXT environment
- variable is applied as appropriate.
- If "verbose" is true then a tuple of the form
- (<fullpath>, <matched-where-description>)
- is returned for each match. The latter element is a textual description
- of where the match was found. For example:
- from PATH element 0
- from HKLM\SOFTWARE\...\perl.exe
- """
- _cmdlnUsage = """
- Show the full path of commands.
- Usage:
- which [<options>...] [<command-name>...]
- Options:
- -h, --help Print this help and exit.
- -V, --version Print the version info and exit.
- -a, --all Print *all* matching paths.
- -v, --verbose Print out how matches were located and
- show near misses on stderr.
- -q, --quiet Just print out matches. I.e., do not print out
- near misses.
- -p <altpath>, --path=<altpath>
- An alternative path (list of directories) may
- be specified for searching.
- -e <exts>, --exts=<exts>
- Specify a list of extensions to consider instead
- of the usual list (';'-separate list, Windows
- only).
- Show the full path to the program that would be run for each given
- command name, if any. Which, like GNU's which, returns the number of
- failed arguments, or -1 when no <command-name> was given.
- Near misses include duplicates, non-regular files and (on Un*x)
- files without executable access.
- """
- __revision__ = "$Id: which.py 430 2005-08-20 03:11:58Z trentm $"
- __version_info__ = (1, 1, 0)
- __version__ = '.'.join(map(str, __version_info__))
- import os
- import sys
- import getopt
- import stat
- #---- exceptions
- class WhichError(Exception):
- pass
- #---- internal support stuff
- def _getRegisteredExecutable(exeName):
- """Windows allow application paths to be registered in the registry."""
- registered = None
- if sys.platform.startswith('win'):
- if os.path.splitext(exeName)[1].lower() != '.exe':
- exeName += '.exe'
- import _winreg
- try:
- key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +\
- exeName
- value = _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE, key)
- registered = (value, "from HKLM\\"+key)
- except _winreg.error:
- pass
- if registered and not os.path.exists(registered[0]):
- registered = None
- return registered
- def _samefile(fname1, fname2):
- if sys.platform.startswith('win'):
- return ( os.path.normpath(os.path.normcase(fname1)) ==\
- os.path.normpath(os.path.normcase(fname2)) )
- else:
- return os.path.samefile(fname1, fname2)
- def _cull(potential, matches, verbose=0):
- """Cull inappropriate matches. Possible reasons:
- - a duplicate of a previous match
- - not a disk file
- - not executable (non-Windows)
- If 'potential' is approved it is returned and added to 'matches'.
- Otherwise, None is returned.
- """
- for match in matches: # don't yield duplicates
- if _samefile(potential[0], match[0]):
- if verbose:
- sys.stderr.write("duplicate: %s (%s)\n" % potential)
- return None
- else:
- if not stat.S_ISREG(os.stat(potential[0]).st_mode):
- if verbose:
- sys.stderr.write("not a regular file: %s (%s)\n" % potential)
- elif not os.access(potential[0], os.X_OK):
- if verbose:
- sys.stderr.write("no executable access: %s (%s)\n"\
- % potential)
- else:
- matches.append(potential)
- return potential
-
- #---- module API
- def whichgen(command, path=None, verbose=0, exts=None):
- """Return a generator of full paths to the given command.
-
- "command" is a the name of the executable to search for.
- "path" is an optional alternate path list to search. The default it
- to use the PATH environment variable.
- "verbose", if true, will cause a 2-tuple to be returned for each
- match. The second element is a textual description of where the
- match was found.
- "exts" optionally allows one to specify a list of extensions to use
- instead of the standard list for this system. This can
- effectively be used as an optimization to, for example, avoid
- stat's of "foo.vbs" when searching for "foo" and you know it is
- not a VisualBasic script but ".vbs" is on PATHEXT. This option
- is only supported on Windows.
- This method returns a generator which yields either full paths to
- the given command or, if verbose, tuples of the form (<path to
- command>, <where path found>).
- """
- matches = []
- if path is None:
- usingGivenPath = 0
- path = os.environ.get("PATH", "").split(os.pathsep)
- if sys.platform.startswith("win"):
- path.insert(0, os.curdir) # implied by Windows shell
- else:
- usingGivenPath = 1
- # Windows has the concept of a list of extensions (PATHEXT env var).
- if sys.platform.startswith("win"):
- if exts is None:
- exts = os.environ.get("PATHEXT", "").split(os.pathsep)
- # If '.exe' is not in exts then obviously this is Win9x and
- # or a bogus PATHEXT, then use a reasonable default.
- for ext in exts:
- if ext.lower() == ".exe":
- break
- else:
- exts = ['.COM', '.EXE', '.BAT']
- elif not isinstance(exts, list):
- raise TypeError("'exts' argument must be a list or None")
- else:
- if exts is not None:
- raise WhichError("'exts' argument is not supported on "\
- "platform '%s'" % sys.platform)
- exts = []
- # File name cannot have path separators because PATH lookup does not
- # work that way.
- if os.sep in command or os.altsep and os.altsep in command:
- pass
- else:
- for i in range(len(path)):
- dirName = path[i]
- # On windows the dirName *could* be quoted, drop the quotes
- if sys.platform.startswith("win") and len(dirName) >= 2\
- and dirName[0] == '"' and dirName[-1] == '"':
- dirName = dirName[1:-1]
- for ext in ['']+exts:
- absName = os.path.abspath(
- os.path.normpath(os.path.join(dirName, command+ext)))
- if os.path.isfile(absName):
- if usingGivenPath:
- fromWhere = "from given path element %d" % i
- elif not sys.platform.startswith("win"):
- fromWhere = "from PATH element %d" % i
- elif i == 0:
- fromWhere = "from current directory"
- else:
- fromWhere = "from PATH element %d" % (i-1)
- match = _cull((absName, fromWhere), matches, verbose)
- if match:
- if verbose:
- yield match
- else:
- yield match[0]
- match = _getRegisteredExecutable(command)
- if match is not None:
- match = _cull(match, matches, verbose)
- if match:
- if verbose:
- yield match
- else:
- yield match[0]
- def which(command, path=None, verbose=0, exts=None):
- """Return the full path to the first match of the given command on
- the path.
-
- "command" is a the name of the executable to search for.
- "path" is an optional alternate path list to search. The default it
- to use the PATH environment variable.
- "verbose", if true, will cause a 2-tuple to be returned. The second
- element is a textual description of where the match was found.
- "exts" optionally allows one to specify a list of extensions to use
- instead of the standard list for this system. This can
- effectively be used as an optimization to, for example, avoid
- stat's of "foo.vbs" when searching for "foo" and you know it is
- not a VisualBasic script but ".vbs" is on PATHEXT. This option
- is only supported on Windows.
- If no match is found for the command, a WhichError is raised.
- """
- try:
- match = whichgen(command, path, verbose, exts).next()
- except StopIteration:
- raise WhichError("Could not find '%s' on the path." % command)
- return match
- def whichall(command, path=None, verbose=0, exts=None):
- """Return a list of full paths to all matches of the given command
- on the path.
- "command" is a the name of the executable to search for.
- "path" is an optional alternate path list to search. The default it
- to use the PATH environment variable.
- "verbose", if true, will cause a 2-tuple to be returned for each
- match. The second element is a textual description of where the
- match was found.
- "exts" optionally allows one to specify a list of extensions to use
- instead of the standard list for this system. This can
- effectively be used as an optimization to, for example, avoid
- stat's of "foo.vbs" when searching for "foo" and you know it is
- not a VisualBasic script but ".vbs" is on PATHEXT. This option
- is only supported on Windows.
- """
- return list( whichgen(command, path, verbose, exts) )
- #---- mainline
- def main(argv):
- all = 0
- verbose = 0
- altpath = None
- exts = None
- try:
- optlist, args = getopt.getopt(argv[1:], 'haVvqp:e:',
- ['help', 'all', 'version', 'verbose', 'quiet', 'path=', 'exts='])
- except getopt.GetoptError, msg:
- sys.stderr.write("which: error: %s. Your invocation was: %s\n"\
- % (msg, argv))
- sys.stderr.write("Try 'which --help'.\n")
- return 1
- for opt, optarg in optlist:
- if opt in ('-h', '--help'):
- print _cmdlnUsage
- return 0
- elif opt in ('-V', '--version'):
- print "which %s" % __version__
- return 0
- elif opt in ('-a', '--all'):
- all = 1
- elif opt in ('-v', '--verbose'):
- verbose = 1
- elif opt in ('-q', '--quiet'):
- verbose = 0
- elif opt in ('-p', '--path'):
- if optarg:
- altpath = optarg.split(os.pathsep)
- else:
- altpath = []
- elif opt in ('-e', '--exts'):
- if optarg:
- exts = optarg.split(os.pathsep)
- else:
- exts = []
- if len(args) == 0:
- return -1
- failures = 0
- for arg in args:
- #print "debug: search for %r" % arg
- nmatches = 0
- for match in whichgen(arg, path=altpath, verbose=verbose, exts=exts):
- if verbose:
- print "%s (%s)" % match
- else:
- print match
- nmatches += 1
- if not all:
- break
- if not nmatches:
- failures += 1
- return failures
- if __name__ == "__main__":
- sys.exit( main(sys.argv) )
|