Index: Doc/library/shutil.rst =================================================================== --- Doc/library/shutil.rst (revision 76432) +++ Doc/library/shutil.rst (working copy) @@ -161,6 +161,16 @@ .. versionadded:: 2.3 +.. function:: which(file [, mode=os.F_OK | os.X_OK[, path=None]]) + + Return a generator which yields full paths in which the *file* name exists + in a directory on *path* and has the given *mode*. By default, *mode* + matches an inclusive OR of os.F_OK and os.X_OK - an existing executable file. + The *path* is, by default, the ``PATH`` variable on the platform, or the + string passed in as *path*. In the event that a ``PATH`` variable is not + found, :const:`os.defpath` is used. + + .. exception:: Error This exception collects exceptions that raised during a multi-file operation. For @@ -237,4 +247,3 @@ return [] # nothing will be ignored copytree(source, destination, ignore=_logpath) - Index: Lib/shutil.py =================================================================== --- Lib/shutil.py (revision 76432) +++ Lib/shutil.py (working copy) @@ -278,6 +278,19 @@ copy2(src, real_dst) os.unlink(src) +def which(file, mode=os.F_OK | os.X_OK, path=None): + """Locate a file in the user's path, or a supplied path. The function + yields full paths in which the given file matches a file in a directory on + the path. + """ + if not path: + path = os.environ.get("PATH", os.defpath) + + for dir in path.split(os.pathsep): + full_path = os.path.join(dir, file) + if os.path.exists(full_path) and os.access(full_path, mode): + yield full_path + def _destinsrc(src, dst): src = abspath(src) dst = abspath(dst) Index: Lib/test/test_shutil.py =================================================================== --- Lib/test/test_shutil.py (revision 76432) +++ Lib/test/test_shutil.py (working copy) @@ -277,7 +277,29 @@ shutil.rmtree(TESTFN, ignore_errors=True) shutil.rmtree(TESTFN2, ignore_errors=True) + def test_which(self): + temp_dir = tempfile.mkdtemp() + temp_file = tempfile.NamedTemporaryFile(prefix="test") + dir, file = os.path.split(temp_file.name) + os.chmod(temp_file.name, stat.S_IXUSR) + files = shutil.which(file, path=dir) + self.assertEqual(list(files), [temp_file.name]) + + os.chmod(temp_file.name, os.R_OK) + files = shutil.which(file, path=dir) + # The default mask shouldn't match this file + self.assertEqual(list(files), []) + + files = shutil.which(file, mode=shutil.stat.S_IRUSR, path=dir) + # The given mask should match this file + self.assertEqual(list(files), [temp_file.name]) + + files = shutil.which("foo", path=dir) + # `foo` should'nt exist in `dir` + self.assertEqual(list(files), []) + + class TestMove(unittest.TestCase): def setUp(self):