Issue #16993: shutil.which() now preserves the case of the path and extension

on Windows.
This commit is contained in:
Serhiy Storchaka 2013-01-21 15:01:34 +02:00
commit 5b987c2e68
4 changed files with 15 additions and 9 deletions

View File

@ -348,7 +348,7 @@ Directory and files operations
directories. For example, on Windows:: directories. For example, on Windows::
>>> shutil.which("python") >>> shutil.which("python")
'c:\\python33\\python.exe' 'C:\\Python33\\python.exe'
.. versionadded:: 3.3 .. versionadded:: 3.3

View File

@ -1092,10 +1092,12 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
pathext = os.environ.get("PATHEXT", "").split(os.pathsep) pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
# See if the given file matches any of the expected path extensions. # See if the given file matches any of the expected path extensions.
# This will allow us to short circuit when given "python.exe". # This will allow us to short circuit when given "python.exe".
matches = [cmd for ext in pathext if cmd.lower().endswith(ext.lower())]
# If it does match, only test that one, otherwise we have to try # If it does match, only test that one, otherwise we have to try
# others. # others.
files = [cmd] if matches else [cmd + ext.lower() for ext in pathext] if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
files = [cmd]
else:
files = [cmd + ext for ext in pathext]
else: else:
# On other platforms you don't have things like PATHEXT to tell you # On other platforms you don't have things like PATHEXT to tell you
# what file suffixes are executable, so just pass on cmd as-is. # what file suffixes are executable, so just pass on cmd as-is.
@ -1103,9 +1105,9 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
seen = set() seen = set()
for dir in path: for dir in path:
dir = os.path.normcase(dir) normdir = os.path.normcase(dir)
if not dir in seen: if not normdir in seen:
seen.add(dir) seen.add(normdir)
for thefile in files: for thefile in files:
name = os.path.join(dir, thefile) name = os.path.join(dir, thefile)
if _access_check(name, mode): if _access_check(name, mode):

View File

@ -1280,12 +1280,13 @@ class TestShutil(unittest.TestCase):
class TestWhich(unittest.TestCase): class TestWhich(unittest.TestCase):
def setUp(self): def setUp(self):
self.temp_dir = tempfile.mkdtemp() self.temp_dir = tempfile.mkdtemp(prefix="Tmp")
self.addCleanup(shutil.rmtree, self.temp_dir, True) self.addCleanup(shutil.rmtree, self.temp_dir, True)
# Give the temp_file an ".exe" suffix for all. # Give the temp_file an ".exe" suffix for all.
# It's needed on Windows and not harmful on other platforms. # It's needed on Windows and not harmful on other platforms.
self.temp_file = tempfile.NamedTemporaryFile(dir=self.temp_dir, self.temp_file = tempfile.NamedTemporaryFile(dir=self.temp_dir,
suffix=".exe") prefix="Tmp",
suffix=".Exe")
os.chmod(self.temp_file.name, stat.S_IXUSR) os.chmod(self.temp_file.name, stat.S_IXUSR)
self.addCleanup(self.temp_file.close) self.addCleanup(self.temp_file.close)
self.dir, self.file = os.path.split(self.temp_file.name) self.dir, self.file = os.path.split(self.temp_file.name)
@ -1328,7 +1329,7 @@ class TestWhich(unittest.TestCase):
# Ask for the file without the ".exe" extension, then ensure that # Ask for the file without the ".exe" extension, then ensure that
# it gets found properly with the extension. # it gets found properly with the extension.
rv = shutil.which(self.temp_file.name[:-4], path=self.dir) rv = shutil.which(self.temp_file.name[:-4], path=self.dir)
self.assertEqual(self.temp_file.name, rv) self.assertEqual(rv, self.temp_file.name[:-4] + ".exe")
class TestMove(unittest.TestCase): class TestMove(unittest.TestCase):

View File

@ -220,6 +220,9 @@ Core and Builtins
Library Library
------- -------
- Issue #16993: shutil.which() now preserves the case of the path and extension
on Windows.
- Issue #16992: On Windows in signal.set_wakeup_fd, validate the file - Issue #16992: On Windows in signal.set_wakeup_fd, validate the file
descriptor argument. descriptor argument.