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
|