Given a command, mode, and a PATH string, return the path which conforms to the given mode on the PATH, or None if there is no such file. `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result of os.environ.get("PATH"), or can be overridden with a custom search path
(cmd, mode=os.F_OK | os.X_OK, path=None)
| 14 | # Everything below this point has been copied verbatim from the Python-3.3 |
| 15 | # sources. |
| 16 | def which(cmd, mode=os.F_OK | os.X_OK, path=None): |
| 17 | """Given a command, mode, and a PATH string, return the path which |
| 18 | conforms to the given mode on the PATH, or None if there is no such |
| 19 | file. |
| 20 | |
| 21 | `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result |
| 22 | of os.environ.get("PATH"), or can be overridden with a custom search |
| 23 | path. |
| 24 | |
| 25 | """ |
| 26 | |
| 27 | # Check that a given file can be accessed with the correct mode. |
| 28 | # Additionally check that `file` is not a directory, as on Windows |
| 29 | # directories pass the os.access check. |
| 30 | def _access_check(fn, mode): |
| 31 | return os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn) |
| 32 | |
| 33 | # Short circuit. If we're given a full path which matches the mode |
| 34 | # and it exists, we're done here. |
| 35 | if _access_check(cmd, mode): |
| 36 | return cmd |
| 37 | |
| 38 | path = (path or os.environ.get("PATH", os.defpath)).split(os.pathsep) |
| 39 | |
| 40 | if sys.platform == "win32": |
| 41 | # The current directory takes precedence on Windows. |
| 42 | if os.curdir not in path: |
| 43 | path.insert(0, os.curdir) |
| 44 | |
| 45 | # PATHEXT is necessary to check on Windows. |
| 46 | pathext = os.environ.get("PATHEXT", "").split(os.pathsep) |
| 47 | # See if the given file matches any of the expected path extensions. |
| 48 | # This will allow us to short circuit when given "python.exe". |
| 49 | matches = [cmd for ext in pathext if cmd.lower().endswith(ext.lower())] |
| 50 | # If it does match, only test that one, otherwise we have to try |
| 51 | # others. |
| 52 | files = [cmd] if matches else [cmd + ext.lower() for ext in pathext] |
| 53 | else: |
| 54 | # On other platforms you don't have things like PATHEXT to tell you |
| 55 | # what file suffixes are executable, so just pass on cmd as-is. |
| 56 | files = [cmd] |
| 57 | |
| 58 | seen = set() |
| 59 | for dir in path: |
| 60 | dir = os.path.normcase(dir) |
| 61 | if dir not in seen: |
| 62 | seen.add(dir) |
| 63 | for thefile in files: |
| 64 | name = os.path.join(dir, thefile) |
| 65 | if _access_check(name, mode): |
| 66 | return name |
| 67 | return None |
no test coverage detected