Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

shutil.which should automatically look for file extension on Windows even when cmd includes path #111958

Open
CarlGao4 opened this issue Nov 10, 2023 · 0 comments
Labels
stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@CarlGao4
Copy link

CarlGao4 commented Nov 10, 2023

Bug report

Bug description:

In shutil.which, it will automatically add PATHEXT variable to the end of file and look for all those files:

cpython/Lib/shutil.py

Lines 1494 to 1515 in 7b55a95

if sys.platform == "win32":
# The current directory takes precedence on Windows.
curdir = os.curdir
if use_bytes:
curdir = os.fsencode(curdir)
if curdir not in path:
path.insert(0, curdir)
# PATHEXT is necessary to check on Windows.
pathext_source = os.getenv("PATHEXT") or _WIN_DEFAULT_PATHEXT
pathext = [ext for ext in pathext_source.split(os.pathsep) if ext]
if use_bytes:
pathext = [os.fsencode(ext) for ext in pathext]
# See if the given file matches any of the expected path extensions.
# This will allow us to short circuit when given "python.exe".
# If it does match, only test that one, otherwise we have to try
# others.
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
files = [cmd]
else:
files = [cmd + ext for ext in pathext]

But before finding the executable in PATH, it will actually check whether the input cmd contains a directory:

cpython/Lib/shutil.py

Lines 1462 to 1468 in 7b55a95

# If we're given a path with a directory part, look it up directly rather
# than referring to PATH directories. This includes checking relative to the
# current directory, e.g. ./script
if os.path.dirname(cmd):
if _access_check(cmd, mode):
return cmd
return None

But it won't automatically append the PATHEXT, which will fail to find some files.

Here are some results using the same paths with both PowerShell Get-Command and Python shutil.which

PS C:\> Get-Command ffmpeg

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Application     ffmpeg.exe                                         0.0.0.0    C:\Commands\ffmpeg\ffmpeg.exe

PS C:\> Get-Command C:\Commands\ffmpeg\ffmpeg.exe

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Application     ffmpeg.exe                                         0.0.0.0    C:\Commands\ffmpeg\ffmpeg.exe

PS C:\> Get-Command C:\Commands\ffmpeg\ffmpeg

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Application     ffmpeg.exe                                         0.0.0.0    C:\Commands\ffmpeg\ffmpeg.exe
Python 3.11.6 | packaged by conda-forge | (main, Oct  3 2023, 10:29:11) [MSC v.1935 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.14.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import shutil

In [2]: shutil.which("ffmpeg")
Out[2]: 'C:\\Commands\\ffmpeg\\ffmpeg.EXE'

In [3]: shutil.which(r"C:\Commands\ffmpeg\ffmpeg.exe")
Out[3]: 'C:\\Commands\\ffmpeg\\ffmpeg.exe'

In [4]: shutil.which(r"C:\Commands\ffmpeg\ffmpeg")  # Returns `None`

In [5]:

And in fact, all the paths above can be ran:

In [5]: import subprocess

In [6]: p = subprocess.Popen("ffmpeg -version", stdout=subprocess.PIPE)
   ...: p.wait()
Out[6]: 0

In [7]: p = subprocess.Popen(r"C:\Commands\ffmpeg\ffmpeg.exe -version", stdout=subprocess.PIPE)
   ...: p.wait()
Out[7]: 0

In [8]: p = subprocess.Popen(r"C:\Commands\ffmpeg\ffmpeg -version", stdout=subprocess.PIPE)
   ...: p.wait()
Out[8]: 0

In [9]: p = subprocess.Popen(r"C:\Commands\ffmpeg\ffmepg -version", stdout=subprocess.PIPE)  # A File does not exist
   ...: p.wait()
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[9], line 1
----> 1 p = subprocess.Popen(r"C:\Commands\ffmpeg\ffmepg -version", stdout=subprocess.PIPE)
      2 p.wait()

File C:\ProgramData\miniconda3\envs\AI\Lib\subprocess.py:1026, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1022         if self.text_mode:
   1023             self.stderr = io.TextIOWrapper(self.stderr,
   1024                     encoding=encoding, errors=errors)
-> 1026     self._execute_child(args, executable, preexec_fn, close_fds,
   1027                         pass_fds, cwd, env,
   1028                         startupinfo, creationflags, shell,
   1029                         p2cread, p2cwrite,
   1030                         c2pread, c2pwrite,
   1031                         errread, errwrite,
   1032                         restore_signals,
   1033                         gid, gids, uid, umask,
   1034                         start_new_session, process_group)
   1035 except:
   1036     # Cleanup if the child failed starting.
   1037     for f in filter(None, (self.stdin, self.stdout, self.stderr)):

File C:\ProgramData\miniconda3\envs\AI\Lib\subprocess.py:1538, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, unused_restore_signals, unused_gid, unused_gids, unused_uid, unused_umask, unused_start_new_session, unused_process_group)
   1536 # Start the process
   1537 try:
-> 1538     hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
   1539                              # no special security
   1540                              None, None,
   1541                              int(not close_fds),
   1542                              creationflags,
   1543                              env,
   1544                              cwd,
   1545                              startupinfo)
   1546 finally:
   1547     # Child is launched. Close the parent's copy of those pipe
   1548     # handles that only the child should have open.  You need
   (...)
   1551     # pipe will not close when the child process exits and the
   1552     # ReadFile will hang.
   1553     self._close_pipe_fds(p2cread, p2cwrite,
   1554                          c2pread, c2pwrite,
   1555                          errread, errwrite)

FileNotFoundError: [WinError 2] 

CPython versions tested on:

3.10, 3.11

Operating systems tested on:

Windows

@CarlGao4 CarlGao4 added the type-bug An unexpected behavior, bug, or error label Nov 10, 2023
@iritkatriel iritkatriel added the stdlib Python modules in the Lib dir label Nov 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
2 participants