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