Reputation: 9730
In Python, is there a portable and simple way to test if an executable program exists?
By simple I mean something like the which
command which would be just perfect. I don't want to search PATH manually or something involving trying to execute it with Popen
& al and see if it fails (that's what I'm doing now, but imagine it's launchmissiles
)
Upvotes: 380
Views: 201845
Reputation: 181
I think that this is perhaps the closest to the which command:
import shutil
def get_executable_path(executable_name: str) -> str | None:
"""Check if an executable program exists in the system PATH and
return its path.
:param executable_name: Name of the executable to check.
:return: Full path to the executable if it exists, None otherwise.
"""
return shutil.which(executable_name)
# Example usage
if __name__ == "__main__":
executable = 'wc'
executable_path = get_executable_path(executable)
if executable_path:
print(f"{executable} is installed and available in PATH at:
{executable_path}")
else:
print(f"{executable} is not available.")
So the above results in:
wc is installed and available in PATH at: /usr/bin/wc
Upvotes: 0
Reputation: 11
updated: updated the function so it outputs not boolean but path to binary (as which command in bash)
it sucks that windows has no which command and thus is incompatible with GNU linux, so devs need to implement their own cross os version
it also sucks that 3rd party pip packages need to be used to get at least some cross os compatibility
sharing this version in hope it helps devs (was tested with python3 on windows srv 2019 + ubuntu)
# find a binary program in all in environment variables defined search paths (Linux $PATH or Windows $Env:Path %PATH% variable)
def which(program):
result = False
search_paths = os.environ['PATH'].split(os.pathsep)# get the directories in the PATH environment variable
if is_windows:
program = program+".exe"
for search_path in search_paths:
last_char = search_path[-1:]
if is_windows:
if last_char != "\\": # if last char of search path is not \ then add it
search_path = search_path + "\\"
elif is_linux:
if last_char != "/": # if last char of search path is not / then add it
search_path = search_path + "/"
if os.path.isfile(search_path+program):
result = search_path+program
break
else:
result = ""
return result
Upvotes: 0
Reputation: 42632
Easiest way I can think of:
def which(program):
import os
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ.get("PATH", "").split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
Edit: Updated code sample to include logic for handling case where provided argument is already a full path to the executable, i.e. "which /bin/ls". This mimics the behavior of the UNIX 'which' command.
Edit: Updated to use os.path.isfile() instead of os.path.exists() per comments.
Edit: path.strip('"')
seems like the wrong thing to do here. Neither Windows nor POSIX appear to encourage quoted PATH items.
Upvotes: 338
Reputation: 35731
Use shutil.which() from Python's wonderful standard library.
Documented as
Return the path to an executable which would be run if the given cmd was called. If no cmd would be called, return
None
.
import shutil
path = shutil.which("foo")
if path is None:
print("no executable found for command 'foo'")
else:
print(f"path to executable 'foo': {path}")
Benefits over home-cooked solutions: documentation, interface stability, long-term maintenance. I advocated for this solution here ~10 years ago, and just one month ago (April 2023), a few relevant details improved again under the hood of this high-level interface provided by excellent engineers doing the hard work for you. Batteries included 🔋!
By the way, the name is indeed derived from the famous which
command on Unix-like systems (and the pedantic POSIX person says: "never use which cmd
, use command -v cmd
, it's more portable") :-).
Upvotes: 276
Reputation: 17057
On the basis that it is easier to ask forgiveness than permission (and, importantly, that the command is safe to run) I would just try to use it and catch the error (OSError in this case - I checked for file does not exist and file is not executable and they both give OSError).
It helps if the executable has something like a --version
or --help
flag that is a quick no-op.
import subprocess
myexec = "python2.8"
try:
subprocess.call([myexec, '--version']
except OSError:
print "%s not found on path" % myexec
This is not a general solution, but will be the easiest way for a lot of use cases - those where the code needs to look for a single well known executable which is safe to run, or at least safe to run with a given flag.
Upvotes: 11
Reputation: 18099
import shutil
command = 'ls'
shutil.which(command) is not None
As a one-liner of Jan-Philip Gehrcke Answer:
cmd_exists = lambda x: shutil.which(x) is not None
As a def:
def cmd_exists(cmd):
return shutil.which(cmd) is not None
my_command = 'ls'
any(
(
os.access(os.path.join(path, my_command), os.X_OK)
and os.path.isfile(os.path.join(path, my_command)
)
for path in os.environ["PATH"].split(os.pathsep)
)
This is a one-liner of Jay's Answer, Also here as a lambda func:
cmd_exists = lambda x: any((os.access(os.path.join(path, x), os.X_OK) and os.path.isfile(os.path.join(path, x))) for path in os.environ["PATH"].split(os.pathsep))
cmd_exists('ls')
Or lastly, indented as a function:
def cmd_exists(cmd, path=None):
""" test if path contains an executable file with name
"""
if path is None:
path = os.environ["PATH"].split(os.pathsep)
for prefix in path:
filename = os.path.join(prefix, cmd)
executable = os.access(filename, os.X_OK)
is_not_directory = os.path.isfile(filename)
if executable and is_not_directory:
return True
return False
Upvotes: 83
Reputation: 4767
Just remember to specify the file extension on windows. Otherwise, you have to write a much complicated is_exe
for windows using PATHEXT
environment variable. You may just want to use FindPath.
OTOH, why are you even bothering to search for the executable? The operating system will do it for you as part of popen
call & will raise an exception if the executable is not found. All you need to do is catch the correct exception for given OS. Note that on Windows, subprocess.Popen(exe, shell=True)
will fail silently if exe
is not found.
Incorporating PATHEXT
into the above implementation of which
(in Jay's answer):
def which(program):
def is_exe(fpath):
return os.path.exists(fpath) and os.access(fpath, os.X_OK) and os.path.isfile(fpath)
def ext_candidates(fpath):
yield fpath
for ext in os.environ.get("PATHEXT", "").split(os.pathsep):
yield fpath + ext
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, program)
for candidate in ext_candidates(exe_file):
if is_exe(candidate):
return candidate
return None
Upvotes: 21
Reputation: 2587
Added windows support
def which(program):
path_ext = [""];
ext_list = None
if sys.platform == "win32":
ext_list = [ext.lower() for ext in os.environ["PATHEXT"].split(";")]
def is_exe(fpath):
exe = os.path.isfile(fpath) and os.access(fpath, os.X_OK)
# search for executable under windows
if not exe:
if ext_list:
for ext in ext_list:
exe_path = "%s%s" % (fpath,ext)
if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK):
path_ext[0] = ext
return True
return False
return exe
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return "%s%s" % (program, path_ext[0])
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return "%s%s" % (exe_file, path_ext[0])
return None
Upvotes: 2
Reputation: 870
You can try the external lib called "sh" (http://amoffat.github.io/sh/).
import sh
print sh.which('ls') # prints '/bin/ls' depending on your setup
print sh.which('xxx') # prints None
Upvotes: 1
Reputation: 5959
This seems simple enough and works both in python 2 and 3
try: subprocess.check_output('which executable',shell=True)
except: sys.exit('ERROR: executable not found')
Upvotes: 2
Reputation: 8607
I know that I'm being a bit of a necromancer here, but I stumbled across this question and the accepted solution didn't work for me for all cases Thought it might be useful to submit anyway. In particular, the "executable" mode detection, and the requirement of supplying the file extension. Furthermore, both python3.3's shutil.which
(uses PATHEXT
) and python2.4+'s distutils.spawn.find_executable
(just tries adding '.exe'
) only work in a subset of cases.
So I wrote a "super" version (based on the accepted answer, and the PATHEXT
suggestion from Suraj). This version of which
does the task a bit more thoroughly, and tries a series of "broadphase" breadth-first techniques first, and eventually tries more fine-grained searches over the PATH
space:
import os
import sys
import stat
import tempfile
def is_case_sensitive_filesystem():
tmphandle, tmppath = tempfile.mkstemp()
is_insensitive = os.path.exists(tmppath.upper())
os.close(tmphandle)
os.remove(tmppath)
return not is_insensitive
_IS_CASE_SENSITIVE_FILESYSTEM = is_case_sensitive_filesystem()
def which(program, case_sensitive=_IS_CASE_SENSITIVE_FILESYSTEM):
""" Simulates unix `which` command. Returns absolute path if program found """
def is_exe(fpath):
""" Return true if fpath is a file we have access to that is executable """
accessmode = os.F_OK | os.X_OK
if os.path.exists(fpath) and os.access(fpath, accessmode) and not os.path.isdir(fpath):
filemode = os.stat(fpath).st_mode
ret = bool(filemode & stat.S_IXUSR or filemode & stat.S_IXGRP or filemode & stat.S_IXOTH)
return ret
def list_file_exts(directory, search_filename=None, ignore_case=True):
""" Return list of (filename, extension) tuples which match the search_filename"""
if ignore_case:
search_filename = search_filename.lower()
for root, dirs, files in os.walk(path):
for f in files:
filename, extension = os.path.splitext(f)
if ignore_case:
filename = filename.lower()
if not search_filename or filename == search_filename:
yield (filename, extension)
break
fpath, fname = os.path.split(program)
# is a path: try direct program path
if fpath:
if is_exe(program):
return program
elif "win" in sys.platform:
# isnt a path: try fname in current directory on windows
if is_exe(fname):
return program
paths = [path.strip('"') for path in os.environ.get("PATH", "").split(os.pathsep)]
exe_exts = [ext for ext in os.environ.get("PATHEXT", "").split(os.pathsep)]
if not case_sensitive:
exe_exts = map(str.lower, exe_exts)
# try append program path per directory
for path in paths:
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
# try with known executable extensions per program path per directory
for path in paths:
filepath = os.path.join(path, program)
for extension in exe_exts:
exe_file = filepath+extension
if is_exe(exe_file):
return exe_file
# try search program name with "soft" extension search
if len(os.path.splitext(fname)[1]) == 0:
for path in paths:
file_exts = list_file_exts(path, fname, not case_sensitive)
for file_ext in file_exts:
filename = "".join(file_ext)
exe_file = os.path.join(path, filename)
if is_exe(exe_file):
return exe_file
return None
Usage looks like this:
>>> which.which("meld")
'C:\\Program Files (x86)\\Meld\\meld\\meld.exe'
The accepted solution did not work for me in this case, since there were files like meld.1
, meld.ico
, meld.doap
, etc also in the directory, one of which were returned instead (presumably since lexicographically first) because the executable test in the accepted answer was incomplete and giving false positives.
Upvotes: 5
Reputation: 166122
This seems to be working for me:
Edited to work on Linux, thanks to Mestreion
def cmd_exists(cmd):
return subprocess.call("type " + cmd, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0
What we're doing here is using the builtin command type
and checking the exit code. If there's no such command, type
will exit with 1 (or a non-zero status code anyway).
The bit about stdout and stderr is just to silence the output of the type
command, since we're only interested in the exit status code.
Example usage:
>>> cmd_exists("jsmin")
True
>>> cmd_exists("cssmin")
False
>>> cmd_exists("ls")
True
>>> cmd_exists("dir")
False
>>> cmd_exists("node")
True
>>> cmd_exists("steam")
False
Upvotes: 14
Reputation: 652
An important question is "Why do you need to test if executable exist?" Maybe you don't? ;-)
Recently I needed this functionality to launch viewer for PNG file. I wanted to iterate over some predefined viewers and run the first that exists. Fortunately, I came across os.startfile
. It's much better! Simple, portable and uses the default viewer on the system:
>>> os.startfile('yourfile.png')
Update: I was wrong about os.startfile
being portable... It's Windows only. On Mac you have to run open
command. And xdg_open
on Unix. There's a Python issue on adding Mac and Unix support for os.startfile
.
Upvotes: 1
Reputation: 414139
There is a which.py script in a standard Python distribution (e.g. on Windows '\PythonXX\Tools\Scripts\which.py'
).
EDIT: which.py
depends on ls
therefore it is not cross-platform.
Upvotes: 0
Reputation: 86354
See os.path module for some useful functions on pathnames. To check if an existing file is executable, use os.access(path, mode), with the os.X_OK mode.
os.X_OK
Value to include in the mode parameter of access() to determine if path can be executed.
EDIT: The suggested which()
implementations are missing one clue - using os.path.join()
to build full file names.
Upvotes: 7
Reputation: 12895
So basically you want to find a file in mounted filesystem (not necessarily in PATH directories only) and check if it is executable. This translates to following plan:
I'd say, doing this in a portable way will require lots of computing power and time. Is it really what you need?
Upvotes: -1