Test if executable exists in Python?
Asked Answered
N

15

372

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)

Noose answered 18/12, 2008 at 5:55 Comment(8)
What's wrong with searching the PATH environment variable? What do you think the UNIX 'which' command does?Connection
Is which.py script from stdlib a simple way?Wendiwendie
@J.F. - the which.py script incl. with Python depends on 'ls' and some of the other comments indicate that Piotr was looking for a cross-platform answer.Connection
@Jay: Thanks for the comment. I has coreutils installed on Windows so I didn't notice that which.py is unix-specific.Wendiwendie
There is also which, the third-party module: code.activestate.com/pypm/whichAccumulate
related: #647455 The question is about Windows but the code works on Posix tooWendiwendie
Keep in mind that os.access() does consider Windows ACLs only in Python 3.5 or later (see bugs.python.org/issue2528).Amusement
maybe https://mcmap.net/q/92207/-test-if-executable-exists-in-python should be the accepted answer in 2020? :)Alphabetical
C
338

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.

Connection answered 18/12, 2008 at 6:5 Comment(18)
Thanks Jay, I accept your answer, though for me it answers my question by the negative. No such function exists in the libs, I just have to write it (I admit my formulation was not clear enough in the fact that I know what which does).Noose
Jay, if you complete your answer according to mine (to have complete 'w') so I can remove mine.Noose
Ah, that makes more sense, gotcha now.Connection
@Piotr - updated the code for cases with full paths provided as argumentConnection
For some OS's you may need to add the executable's extension. For example, on Ubuntu I can write which("scp") but on Windows, I needed to write which("scp.exe").Darbydarce
I'd suggest changing "os.path.exists" to "os.path.isfile". Otherwise in Unix this might falsely match a directory with the +x bit set. I also find it useful to add this to the top of the function: import sys; if sys.platform == "win32" and not program.endswith(".exe"): program += ".exe". This way under Windows you can refer to either "calc" or "calc.exe", just like you could in a cmd window.Improve
@KevinIvarsen A better option would be looping through the values of the PATHEXT env var because command is as valid as command.com as is script vs script.batRepairman
You could re-write is_exec as is_exec = lambda x: os.path.isfile(x) and os.access(x, os.X_OK). Why? just because I like lambda.Cankerworm
bash also expands tildes in path elements; use os.path.expanduser to recreate this.Microorganism
Won't os.access(x, os.X_OK) return True for pretty much anything on Windows? (Added: see here for discussion of this.)Prunelle
isnt os.access(x, os.X_OK) enough?Girth
@Girth On Unix like systems both files and directories may have the X flag set. unix.stackexchange.com/questions/21251/…Sophrosyne
Be careful (as always) on windows. os.access(f, os.X_OK) will return true for a text file which is the same behaviour as the where program. A more reliable way is to check if the file exists and if its extension matches one of the entries in the PATHEXT environment variable.Embattled
For Windows the solution should include searching the App Paths registry entry as described in #18045437, as these applications can also be found without including them in the PATH variable. shutils.which() doesn't seem to do this.Armstead
I'm new to python, so sorry if this is obvious, but why is fpath being defined and checked with if fpath at the beginning, it looks like it will just be false and skipped over? does the response from split get put into fpath and fname?Neese
aaaah never mind, I understand now, os.path.split does works it out, very fancy, this pythong stuff is great, thanksNeese
If someone wonders about the App Paths registry not working: this does not work from cmd, it does work from ps, but only with Diagnostics.Process and Start-Process.Alarcon
Thank you for this; you're a scholar and a gentleman.Radom
D
384

I know this is an ancient question, but you can use distutils.spawn.find_executable. This has been documented since python 2.4 and has existed since python 1.6.

import distutils.spawn
distutils.spawn.find_executable("notepad.exe")

Also, Python 3.3 now offers shutil.which().

Doublecheck answered 26/9, 2012 at 22:40 Comment(7)
On win32, the distutils.spawn.find_executable implementation only looks for .exe rather than using the list of extensions to search for set in %PATHEXT%. That's not great, but it might work for the all the cases someone needs.Eardrum
example usage: from distutils import spawn php_path = spawn.find_executable("php")Twylatwyman
Apparently distutils.spawn isn't available reliably: with my System install (/usr/bin/python) of Python 2.7.6 on OS X 10.10, I get: AttributeError: 'module' object has no attribute 'spawn' , although strangely it works on the same machine with the same version of Python, but from a virtualenv install.Reflation
@JoshKupershmidt, make sure to import distutils.spawn, or follow the from distutils import spawn syntax rather than just import distutils. Otherwise it may not be accessible and you will get the above AttributeError even if it is there.Backlash
For the record: the "Python 3.3 now offers shutil.which()" was rather brutally copy/pasted without attribution on Jan 27, 2013 from https://mcmap.net/q/92207/-test-if-executable-exists-in-python.Alphabetical
Since Python 3.10 the distutils package is deprecated. It will be removed in Python 3.12.Folketing
The distutils.spawn.find_executable function can find an executable based on such a path: "./executable_name", the other one cannot.Abrahamsen
C
338

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.

Connection answered 18/12, 2008 at 6:5 Comment(18)
Thanks Jay, I accept your answer, though for me it answers my question by the negative. No such function exists in the libs, I just have to write it (I admit my formulation was not clear enough in the fact that I know what which does).Noose
Jay, if you complete your answer according to mine (to have complete 'w') so I can remove mine.Noose
Ah, that makes more sense, gotcha now.Connection
@Piotr - updated the code for cases with full paths provided as argumentConnection
For some OS's you may need to add the executable's extension. For example, on Ubuntu I can write which("scp") but on Windows, I needed to write which("scp.exe").Darbydarce
I'd suggest changing "os.path.exists" to "os.path.isfile". Otherwise in Unix this might falsely match a directory with the +x bit set. I also find it useful to add this to the top of the function: import sys; if sys.platform == "win32" and not program.endswith(".exe"): program += ".exe". This way under Windows you can refer to either "calc" or "calc.exe", just like you could in a cmd window.Improve
@KevinIvarsen A better option would be looping through the values of the PATHEXT env var because command is as valid as command.com as is script vs script.batRepairman
You could re-write is_exec as is_exec = lambda x: os.path.isfile(x) and os.access(x, os.X_OK). Why? just because I like lambda.Cankerworm
bash also expands tildes in path elements; use os.path.expanduser to recreate this.Microorganism
Won't os.access(x, os.X_OK) return True for pretty much anything on Windows? (Added: see here for discussion of this.)Prunelle
isnt os.access(x, os.X_OK) enough?Girth
@Girth On Unix like systems both files and directories may have the X flag set. unix.stackexchange.com/questions/21251/…Sophrosyne
Be careful (as always) on windows. os.access(f, os.X_OK) will return true for a text file which is the same behaviour as the where program. A more reliable way is to check if the file exists and if its extension matches one of the entries in the PATHEXT environment variable.Embattled
For Windows the solution should include searching the App Paths registry entry as described in #18045437, as these applications can also be found without including them in the PATH variable. shutils.which() doesn't seem to do this.Armstead
I'm new to python, so sorry if this is obvious, but why is fpath being defined and checked with if fpath at the beginning, it looks like it will just be false and skipped over? does the response from split get put into fpath and fname?Neese
aaaah never mind, I understand now, os.path.split does works it out, very fancy, this pythong stuff is great, thanksNeese
If someone wonders about the App Paths registry not working: this does not work from cmd, it does work from ps, but only with Diagnostics.Process and Start-Process.Alarcon
Thank you for this; you're a scholar and a gentleman.Radom
A
267

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") :-).

Alphabetical answered 18/12, 2012 at 16:5 Comment(3)
By the way, this answer was the first one in this thread to suggest using shutil.which() if that matters to you ¯_(ツ)_/¯.Alphabetical
For interactive use, zsh has where which lists all matching executables in the path, not just the first one, and also includes shell functions and aliases in the search. Yes, yes, pedantic zsh user, I know. :-)Population
Probably the best solution if you have 3.3 or later. I need to support an older version of Python (I know, I know), so going with Jay's solution.Radom
E
82

For python 3.3 and later:

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

For python 3.2 and earlier:

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
Edmondo answered 18/12, 2008 at 5:55 Comment(5)
the "indented as a function" version uses the variable x where it should be cmdParonomasia
you must also add a test to see if os.path.join(path, cmd) is a file, no? Afterall, directories can also have the executable bit set...Witkin
@Witkin That does sound like a possible case, would you mind confirming this behavior and updating this answer? I am happy to change this post to a community wiki if that helps.Edmondo
@ThorSummoner: I've confirmed it, and it does indeed require the test for file. A simple test: mkdir -p -- "$HOME"/bin/dummy && PATH="$PATH":"$HOME"/bin && python -c 'import os; print any(os.access(os.path.join(path, "dummy"), os.X_OK) for path in os.environ["PATH"].split(os.pathsep))' && rmdir -- "$HOME"/bin/dummyWitkin
Adding a simple and os.path.isfile(...) to the appropriate places is enough to fix thatWitkin
F
21

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
Fanfani answered 18/12, 2008 at 22:25 Comment(2)
It fixed a bug in the accepted answer, feel this answer should be on top instead.Empathize
clever usage of yield in ext_candidates, gave me a better understanding of how that keyword worksMicroclimatology
S
14

For *nix platforms (Linux and OS X)

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
Shanly answered 17/6, 2012 at 8:1 Comment(6)
I've tried it in Ubuntu 12.04, it throws OSError: [Errno 2] No such file or directory. Maybe in Mac type is an actual commandWitkin
using shell=True is mandatory in Linux since type is a shell builtin there (I wonder why it isn't in Mac). But using it with user-supplied arbitrary strings (such as cmd) is a major security risk, as pointed by the docsWitkin
cool, thanks for fixing it. I edited the answer to include your fix. In the future feel free to edit wrong answers yourself to make them more correct!Shanly
Good to know, thanks, but just FYI: this doesn't work on tcsh (6.17.00), because type is not a builtin in tcsh or csh. So it's not very universal.Jammiejammin
Attention: be sure that the variable "cmd" contains valid data. If it comes from an external source, a bad guy could give you "ls; rm -rf /". I think the in-python solution (without subprocess) is much better. Next point: If you call this method often, the subprocess solution is much slower, since a lot of processes need to spawned.Insinuate
Macs include a little wrapper as /usr/bin/type that basically just wraps the shell builtin.Sycee
A
10

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.

Acetify answered 26/7, 2012 at 8:6 Comment(5)
It's too dangerous even to call --version on a program named launchmissiles!Foreskin
+1, I like this approach. EAFP is a golden Python rule. Except perhaps for setting up UI, why would you want to know if launchmissies exists unless you want to launch missiles? Better to execute it and act upon exit status / exceptionsWitkin
The problem with this method is that output is printed to the console. If you use pipes and shell=True, then the OSError never gets raisedMonadism
On macOS you also have stub executables for e.g. git that you probably don't want to run blindly.Lian
@Witkin Well, you might want to just check that launchmissiles is really there so that you can launchmissiles immediately when needed later on, rather than faff about trying to figure out how to install launchmissiles during an emergency.Frisk
H
7

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.

Hohenstaufen answered 18/12, 2008 at 6:8 Comment(2)
Thanks, gimel, so basically I have my answer: no such function exists, I must do It manually.Noose
Don't use os.access. access function is designed for suid programs.Megalopolis
P
5

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.

Prerogative answered 31/8, 2013 at 10:30 Comment(1)
plenty of opportunity for a mounted drive to be case insensitive - even if the /tmp isn't. and writing a file is not a good mechanism... instead LS the root of each path member... then check to see if a known entry exists with a different, missing case. now even for a read-only mounted network drive (common for shipping software), this works!Bedim
S
2

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')
Sadism answered 13/10, 2013 at 22:43 Comment(4)
Sorry Jaap, but this solution only works when the executable doesn't call an exit code 1 if it's called incorrectly. So, for example, it'll work for "dir" and "ls", but if you execute against something that requires configuration it'll break even though the executable is there.Stereogram
What do you mean exactly by "require configuration"? By itself 'which' doesn't actually execute anything but just checks the PATH for existence of an executable by this name (man which).Sadism
Ohh, so you are using "which" in order to find the executable. So this only works for Linux/Unix?Stereogram
Use command -v executable or type executable to be universal. There are cases where which on Macs does not return expected results.Rudelson
V
2

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
Vestry answered 17/4, 2015 at 4:6 Comment(0)
G
1

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.

Gosplan answered 25/8, 2009 at 13:10 Comment(0)
R
1

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
Ramberg answered 6/11, 2014 at 18:5 Comment(0)
W
0

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.

Wendiwendie answered 19/12, 2008 at 5:11 Comment(0)
R
-1

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:

  • enumerate all files in locally mounted filesystems
  • match results with name pattern
  • for each file found check if it is executable

I'd say, doing this in a portable way will require lots of computing power and time. Is it really what you need?

Radcliff answered 18/12, 2008 at 11:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.