which.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 
3 # Copyright (c) 2002-2007 ActiveState Software Inc.
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a
6 # copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 #
24 # Author:
25 # Trent Mick (TrentM@ActiveState.com)
26 # Home:
27 # http://trentm.com/projects/which/
28 
29 r"""Find the full path to commands.
30 
31 which(command, path=None, verbose=0, exts=None)
32  Return the full path to the first match of the given command on the
33  path.
34 
35 whichall(command, path=None, verbose=0, exts=None)
36  Return a list of full paths to all matches of the given command on
37  the path.
38 
39 whichgen(command, path=None, verbose=0, exts=None)
40  Return a generator which will yield full paths to all matches of the
41  given command on the path.
42 
43 By default the PATH environment variable is searched (as well as, on
44 Windows, the AppPaths key in the registry), but a specific 'path' list
45 to search may be specified as well. On Windows, the PATHEXT environment
46 variable is applied as appropriate.
47 
48 If "verbose" is true then a tuple of the form
49  (<fullpath>, <matched-where-description>)
50 is returned for each match. The latter element is a textual description
51 of where the match was found. For example:
52  from PATH element 0
53  from HKLM\SOFTWARE\...\perl.exe
54 """
55 
56 from __future__ import unicode_literals
57 
58 _cmdlnUsage = """
59  Show the full path of commands.
60 
61  Usage:
62  which [<options>...] [<command-name>...]
63 
64  Options:
65  -h, --help Print this help and exit.
66  -V, --version Print the version info and exit.
67 
68  -a, --all Print *all* matching paths.
69  -v, --verbose Print out how matches were located and
70  show near misses on stderr.
71  -q, --quiet Just print out matches. I.e., do not print out
72  near misses.
73 
74  -p <altpath>, --path=<altpath>
75  An alternative path (list of directories) may
76  be specified for searching.
77  -e <exts>, --exts=<exts>
78  Specify a list of extensions to consider instead
79  of the usual list (';'-separate list, Windows
80  only).
81 
82  Show the full path to the program that would be run for each given
83  command name, if any. Which, like GNU's which, returns the number of
84  failed arguments, or -1 when no <command-name> was given.
85 
86  Near misses include duplicates, non-regular files and (on Un*x)
87  files without executable access.
88 """
89 
90 __revision__ = "Id: which.py 1448 2007-02-28 19:13:06Z trentm"
91 __version_info__ = (1, 1, 3)
92 __version__ = '.'.join(map(str, __version_info__))
93 __all__ = ["which", "whichall", "whichgen", "WhichError"]
94 
95 import os
96 import sys
97 import getopt
98 import stat
99 
100 
101 #---- exceptions
102 
103 class WhichError(Exception):
104  pass
105 
106 
107 
108 #---- internal support stuff
109 
110 def _getRegisteredExecutable(exeName):
111  """Windows allow application paths to be registered in the registry."""
112  registered = None
113  if sys.platform.startswith('win'):
114  if os.path.splitext(exeName)[1].lower() != '.exe':
115  exeName += '.exe'
116  import _winreg
117  try:
118  key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +\
119  exeName
120  value = _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE, key)
121  registered = (value, "from HKLM\\"+key)
122  except _winreg.error:
123  pass
124  if registered and not os.path.exists(registered[0]):
125  registered = None
126  return registered
127 
128 def _samefile(fname1, fname2):
129  if sys.platform.startswith('win'):
130  return ( os.path.normpath(os.path.normcase(fname1)) ==\
131  os.path.normpath(os.path.normcase(fname2)) )
132  else:
133  return os.path.samefile(fname1, fname2)
134 
135 def _cull(potential, matches, verbose=0):
136  """Cull inappropriate matches. Possible reasons:
137  - a duplicate of a previous match
138  - not a disk file
139  - not executable (non-Windows)
140  If 'potential' is approved it is returned and added to 'matches'.
141  Otherwise, None is returned.
142  """
143  for match in matches: # don't yield duplicates
144  if _samefile(potential[0], match[0]):
145  if verbose:
146  sys.stderr.write("duplicate: %s (%s)\n" % potential)
147  return None
148  else:
149  if not stat.S_ISREG(os.stat(potential[0]).st_mode):
150  if verbose:
151  sys.stderr.write("not a regular file: %s (%s)\n" % potential)
152  elif sys.platform != "win32" \
153  and not os.access(potential[0], os.X_OK):
154  if verbose:
155  sys.stderr.write("no executable access: %s (%s)\n"\
156  % potential)
157  else:
158  matches.append(potential)
159  return potential
160 
161 
162 #---- module API
163 
164 def whichgen(command, path=None, verbose=0, exts=None):
165  """Return a generator of full paths to the given command.
166 
167  "command" is a the name of the executable to search for.
168  "path" is an optional alternate path list to search. The default it
169  to use the PATH environment variable.
170  "verbose", if true, will cause a 2-tuple to be returned for each
171  match. The second element is a textual description of where the
172  match was found.
173  "exts" optionally allows one to specify a list of extensions to use
174  instead of the standard list for this system. This can
175  effectively be used as an optimization to, for example, avoid
176  stat's of "foo.vbs" when searching for "foo" and you know it is
177  not a VisualBasic script but ".vbs" is on PATHEXT. This option
178  is only supported on Windows.
179 
180  This method returns a generator which yields either full paths to
181  the given command or, if verbose, tuples of the form (<path to
182  command>, <where path found>).
183  """
184  matches = []
185  if path is None:
186  usingGivenPath = 0
187  path = os.environ.get("PATH", "").split(os.pathsep)
188  if sys.platform.startswith("win"):
189  path.insert(0, os.curdir) # implied by Windows shell
190  else:
191  usingGivenPath = 1
192 
193  # Windows has the concept of a list of extensions (PATHEXT env var).
194  if sys.platform.startswith("win"):
195  if exts is None:
196  exts = os.environ.get("PATHEXT", "").split(os.pathsep)
197  # If '.exe' is not in exts then obviously this is Win9x and
198  # or a bogus PATHEXT, then use a reasonable default.
199  for ext in exts:
200  if ext.lower() == ".exe":
201  break
202  else:
203  exts = ['.CMD', '.COM', '.EXE', '.BAT']
204  elif not isinstance(exts, list):
205  raise TypeError("'exts' argument must be a list or None")
206  else:
207  if exts is not None:
208  raise WhichError("'exts' argument is not supported on "\
209  "platform '%s'" % sys.platform)
210  exts = []
211 
212  # File name cannot have path separators because PATH lookup does not
213  # work that way.
214  if os.sep in command or os.altsep and os.altsep in command:
215  if os.path.exists(command):
216  match = _cull((command, "explicit path given"), matches, verbose)
217  if match:
218  if verbose:
219  yield match
220  else:
221  yield match[0]
222  else:
223  for i in range(len(path)):
224  dirName = path[i]
225  # On windows the dirName *could* be quoted, drop the quotes
226  if sys.platform.startswith("win") and len(dirName) >= 2\
227  and dirName[0] == '"' and dirName[-1] == '"':
228  dirName = dirName[1:-1]
229  for ext in ['']+exts:
230  absName = os.path.abspath(
231  os.path.normpath(os.path.join(dirName, command+ext)))
232  if os.path.isfile(absName):
233  if usingGivenPath:
234  fromWhere = "from given path element %d" % i
235  elif not sys.platform.startswith("win"):
236  fromWhere = "from PATH element %d" % i
237  elif i == 0:
238  fromWhere = "from current directory"
239  else:
240  fromWhere = "from PATH element %d" % (i-1)
241  match = _cull((absName, fromWhere), matches, verbose)
242  if match:
243  if verbose:
244  yield match
245  else:
246  yield match[0]
247  match = _getRegisteredExecutable(command)
248  if match is not None:
249  match = _cull(match, matches, verbose)
250  if match:
251  if verbose:
252  yield match
253  else:
254  yield match[0]
255 
256 
257 def which(command, path=None, verbose=0, exts=None):
258  """Return the full path to the first match of the given command on
259  the path.
260 
261  "command" is a the name of the executable to search for.
262  "path" is an optional alternate path list to search. The default it
263  to use the PATH environment variable.
264  "verbose", if true, will cause a 2-tuple to be returned. The second
265  element is a textual description of where the match was found.
266  "exts" optionally allows one to specify a list of extensions to use
267  instead of the standard list for this system. This can
268  effectively be used as an optimization to, for example, avoid
269  stat's of "foo.vbs" when searching for "foo" and you know it is
270  not a VisualBasic script but ".vbs" is on PATHEXT. This option
271  is only supported on Windows.
272 
273  If no match is found for the command, a WhichError is raised.
274  """
275  try:
276  match = next(whichgen(command, path, verbose, exts))
277  except StopIteration:
278  raise WhichError("Could not find '%s' on the path." % command)
279  return match
280 
281 
282 def whichall(command, path=None, verbose=0, exts=None):
283  """Return a list of full paths to all matches of the given command
284  on the path.
285 
286  "command" is a the name of the executable to search for.
287  "path" is an optional alternate path list to search. The default it
288  to use the PATH environment variable.
289  "verbose", if true, will cause a 2-tuple to be returned for each
290  match. The second element is a textual description of where the
291  match was found.
292  "exts" optionally allows one to specify a list of extensions to use
293  instead of the standard list for this system. This can
294  effectively be used as an optimization to, for example, avoid
295  stat's of "foo.vbs" when searching for "foo" and you know it is
296  not a VisualBasic script but ".vbs" is on PATHEXT. This option
297  is only supported on Windows.
298  """
299  return list( whichgen(command, path, verbose, exts) )
300 
301 
302 
303 #---- mainline
304 
305 def main(argv):
306  all = 0
307  verbose = 0
308  altpath = None
309  exts = None
310  try:
311  optlist, args = getopt.getopt(argv[1:], 'haVvqp:e:',
312  ['help', 'all', 'version', 'verbose', 'quiet', 'path=', 'exts='])
313  except getopt.GetoptError as msg:
314  sys.stderr.write("which: error: %s. Your invocation was: %s\n"\
315  % (msg, argv))
316  sys.stderr.write("Try 'which --help'.\n")
317  return 1
318  for opt, optarg in optlist:
319  if opt in ('-h', '--help'):
320  print(_cmdlnUsage)
321  return 0
322  elif opt in ('-V', '--version'):
323  print("which %s" % __version__)
324  return 0
325  elif opt in ('-a', '--all'):
326  all = 1
327  elif opt in ('-v', '--verbose'):
328  verbose = 1
329  elif opt in ('-q', '--quiet'):
330  verbose = 0
331  elif opt in ('-p', '--path'):
332  if optarg:
333  altpath = optarg.split(os.pathsep)
334  else:
335  altpath = []
336  elif opt in ('-e', '--exts'):
337  if optarg:
338  exts = optarg.split(os.pathsep)
339  else:
340  exts = []
341 
342  if len(args) == 0:
343  return -1
344 
345  failures = 0
346  for arg in args:
347  #print("debug: search for %r" % arg)
348  nmatches = 0
349  for match in whichgen(arg, path=altpath, verbose=verbose, exts=exts):
350  if verbose:
351  print("%s (%s)" % match)
352  else:
353  print(match)
354  nmatches += 1
355  if not all:
356  break
357  if not nmatches:
358  failures += 1
359  return failures
360 
361 
362 if __name__ == "__main__":
363  sys.exit( main(sys.argv) )
364 
365 
def which(command, path=None, verbose=0, exts=None)
Definition: which.py:257
std::string join(const std::string &base, const std::string &path)
Join two paths, e.g., base path and relative path.
Definition: path.cxx:432
void split(const std::string &path, std::string &head, std::string &tail)
Split path into two parts.
Definition: path.cxx:164
def whichgen(command, path=None, verbose=0, exts=None)
Definition: which.py:164
def whichall(command, path=None, verbose=0, exts=None)
Definition: which.py:282
def main(argv)
Definition: which.py:305