Check if a program exists from a python script
Question:
How do I check if a program exists from a python script?
Let’s say you want to check if wget
or curl
are available. We’ll assume that they should be in path.
It would be the best to see a multiplatform solution but for the moment, Linux is enough.
Hints:
- running the command and checking for return code is not always enough as some tools do return non 0 result even when you try
--version
.
- nothing should be visible on screen when checking for the command
Also, I would appreciate a solution that that is more general, like is_tool(name)
Answers:
I would probably shell out to which wget
or which curl
and check that the result ends in the name of the program you are using. The magic of unix 🙂
Actually, all you need to do is check the return code of which
. So… using our trusty subprocess
module:
import subprocess
rc = subprocess.call(['which', 'wget'])
if rc == 0:
print('wget installed!')
else:
print('wget missing in path!')
Note that I tested this on windows with cygwin… If you want to figure out how to implement which
in pure python, i suggest you check here: http://pypi.python.org/pypi/pycoreutils (oh dear – it seems they don’t supply which
. Time for a friendly nudge?)
UPDATE: On Windows, you can use where
instead of which
for a similar effect.
The easiest way is to try to run the program with the desired parameters, and handle the exception if it doesn’t exist:
try:
subprocess.call(["wget", "your", "parameters", "here"])
except FileNotFoundError:
# handle file not found error.
This is a common pattern in Python: EAFP
In Python 2, you had to catch OsError
instead, since the more fine-grained exception classes for OS errors did not exist yet:
try:
subprocess.call(["wget", "your", "parameters", "here"])
except OSError as e:
if e.errno == errno.ENOENT:
# handle file not found error.
else:
# Something else went wrong while trying to run `wget`
raise
import os
import subprocess
def is_tool(prog):
for dir in os.environ['PATH'].split(os.pathsep):
if os.path.exists(os.path.join(dir, prog)):
try:
subprocess.call([os.path.join(dir, prog)],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
except OSError, e:
return False
return True
return False
You could use a subprocess call to the binary needed with :
- “which” : *nix
- “where” : Win 2003 and later (Xp has an addon)
to get the executable path (supposing it is in the environment path).
import os
import platform
import subprocess
cmd = "where" if platform.system() == "Windows" else "which"
try:
subprocess.call([cmd, your_executable_to_check_here])
except:
print "No executable"
or just use Ned Batchelder’s wh.py script, that is a “which” cross platform implementation:
import subprocess
import os
def is_tool(name):
try:
devnull = open(os.devnull)
subprocess.Popen([name], stdout=devnull, stderr=devnull).communicate()
except OSError as e:
if e.errno == os.errno.ENOENT:
return False
return True
A slight modification to @SvenMarnach’s code that addresses the issue of printing to the standard output stream. If you use the subprocess.check_output()
function rather than subprocess.call()
then you can handle the string that is normally printed to standard out in your code and still catch exceptions and the exit status code.
If you want to suppress the standard output stream in the terminal, don’t print the std out string that is returned from check_output
:
import subprocess
import os
try:
stdout_string = subprocess.check_output(["wget", "--help"], stderr=subprocess.STDOUT)
# print(stdout_string)
except subprocess.CalledProcessError as cpe:
print(cpe.returncode)
print(cpe.output)
except OSError as e:
if e.errno == os.errno.ENOENT:
print(e)
else:
# Something else went wrong while trying to run `wget`
print(e)
The non-zero exit status code and output string are raised in the CalledProcessError
as subprocess.CalledProcessError.returncode
and subprocess.CalledProcessError.output
so you can do whatever you’d like with them.
If you want to print the executable’s standard output to the terminal, print the string that is returned:
import subprocess
import os
try:
stdout_string = subprocess.check_output(["wget", "--help"], stderr=subprocess.STDOUT)
print(stdout_string)
except subprocess.CalledProcessError as cpe:
print(cpe.returncode)
print(cpe.output)
except OSError as e:
if e.errno == os.errno.ENOENT:
print(e)
else:
# Something else went wrong while trying to run `wget`
print(e)
print()
adds an extra newline to the string. If you want to eliminate that (and write std error to the std err stream instead of the std out stream as shown with the print() statements above), use sys.stdout.write(string)
and sys.stderr.write(string)
instead of print():
import subprocess
import os
import sys
try:
stdout_string = subprocess.check_output(["bogus"], stderr=subprocess.STDOUT)
sys.stdout.write(stdout_string)
except subprocess.CalledProcessError as cpe:
sys.stderr.write(cpe.returncode)
sys.stderr.write(cpe.output)
except OSError as e:
if e.errno == os.errno.ENOENT:
sys.stderr.write(e.strerror)
else:
# Something else went wrong while trying to run `wget`
sys.stderr.write(e.strerror)
I’d go for:
import distutils.spawn
def is_tool(name):
return distutils.spawn.find_executable(name) is not None
shutil.which
Let me recommend an option that has not been discussed yet: a Python implementation of which
, specifically shutil.which
. It was introduced in Python 3.3 and is cross-platform, supporting Linux, Mac, and Windows. It is also available in Python 2.x via whichcraft. You can also just rip the code for which
right out of whichcraft here and insert it into your program.
def is_tool(name):
"""Check whether `name` is on PATH and marked as executable."""
# from whichcraft import which
from shutil import which
return which(name) is not None
distutils.spawn.find_executable
Another option that has already been mentioned is distutils.spawn.find_executable
.
find_executable
‘s docstring is as follows:
Tries to find ‘executable’ in the directories listed in ‘path’
So if you pay attention, you’ll note that the name of the function is somewhat misleading. Unlike which
, find_executable
does not actually verify that executable
is marked as executable, only that it is on the PATH. So it’s entirely possible (however unlikely) that find_executable
indicates a program is available when it is not.
For example, suppose you have a file /usr/bin/wget
that is not marked executable. Running wget
from the shell will result in the following error: bash: /usr/bin/wget: Permission denied. which('wget') is not None
will return False, yet find_executable('wget') is not None
will return True. You can probably get away with using either function, but this is just something to be aware of with find_executable
.
def is_tool(name):
"""Check whether `name` is on PATH."""
from distutils.spawn import find_executable
return find_executable(name) is not None
I’d change @sorin’s answer as follows, the reason is it would check the name of the program without passing the absolute path of the program
from subprocess import Popen, PIPE
def check_program_exists(name):
p = Popen(['/usr/bin/which', name], stdout=PIPE, stderr=PIPE)
p.communicate()
return p.returncode == 0
How do I check if a program exists from a python script?
Let’s say you want to check if wget
or curl
are available. We’ll assume that they should be in path.
It would be the best to see a multiplatform solution but for the moment, Linux is enough.
Hints:
- running the command and checking for return code is not always enough as some tools do return non 0 result even when you try
--version
. - nothing should be visible on screen when checking for the command
Also, I would appreciate a solution that that is more general, like is_tool(name)
I would probably shell out to which wget
or which curl
and check that the result ends in the name of the program you are using. The magic of unix 🙂
Actually, all you need to do is check the return code of which
. So… using our trusty subprocess
module:
import subprocess
rc = subprocess.call(['which', 'wget'])
if rc == 0:
print('wget installed!')
else:
print('wget missing in path!')
Note that I tested this on windows with cygwin… If you want to figure out how to implement which
in pure python, i suggest you check here: http://pypi.python.org/pypi/pycoreutils (oh dear – it seems they don’t supply which
. Time for a friendly nudge?)
UPDATE: On Windows, you can use where
instead of which
for a similar effect.
The easiest way is to try to run the program with the desired parameters, and handle the exception if it doesn’t exist:
try:
subprocess.call(["wget", "your", "parameters", "here"])
except FileNotFoundError:
# handle file not found error.
This is a common pattern in Python: EAFP
In Python 2, you had to catch OsError
instead, since the more fine-grained exception classes for OS errors did not exist yet:
try:
subprocess.call(["wget", "your", "parameters", "here"])
except OSError as e:
if e.errno == errno.ENOENT:
# handle file not found error.
else:
# Something else went wrong while trying to run `wget`
raise
import os
import subprocess
def is_tool(prog):
for dir in os.environ['PATH'].split(os.pathsep):
if os.path.exists(os.path.join(dir, prog)):
try:
subprocess.call([os.path.join(dir, prog)],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
except OSError, e:
return False
return True
return False
You could use a subprocess call to the binary needed with :
- “which” : *nix
- “where” : Win 2003 and later (Xp has an addon)
to get the executable path (supposing it is in the environment path).
import os
import platform
import subprocess
cmd = "where" if platform.system() == "Windows" else "which"
try:
subprocess.call([cmd, your_executable_to_check_here])
except:
print "No executable"
or just use Ned Batchelder’s wh.py script, that is a “which” cross platform implementation:
import subprocess
import os
def is_tool(name):
try:
devnull = open(os.devnull)
subprocess.Popen([name], stdout=devnull, stderr=devnull).communicate()
except OSError as e:
if e.errno == os.errno.ENOENT:
return False
return True
A slight modification to @SvenMarnach’s code that addresses the issue of printing to the standard output stream. If you use the subprocess.check_output()
function rather than subprocess.call()
then you can handle the string that is normally printed to standard out in your code and still catch exceptions and the exit status code.
If you want to suppress the standard output stream in the terminal, don’t print the std out string that is returned from check_output
:
import subprocess
import os
try:
stdout_string = subprocess.check_output(["wget", "--help"], stderr=subprocess.STDOUT)
# print(stdout_string)
except subprocess.CalledProcessError as cpe:
print(cpe.returncode)
print(cpe.output)
except OSError as e:
if e.errno == os.errno.ENOENT:
print(e)
else:
# Something else went wrong while trying to run `wget`
print(e)
The non-zero exit status code and output string are raised in the CalledProcessError
as subprocess.CalledProcessError.returncode
and subprocess.CalledProcessError.output
so you can do whatever you’d like with them.
If you want to print the executable’s standard output to the terminal, print the string that is returned:
import subprocess
import os
try:
stdout_string = subprocess.check_output(["wget", "--help"], stderr=subprocess.STDOUT)
print(stdout_string)
except subprocess.CalledProcessError as cpe:
print(cpe.returncode)
print(cpe.output)
except OSError as e:
if e.errno == os.errno.ENOENT:
print(e)
else:
# Something else went wrong while trying to run `wget`
print(e)
print()
adds an extra newline to the string. If you want to eliminate that (and write std error to the std err stream instead of the std out stream as shown with the print() statements above), use sys.stdout.write(string)
and sys.stderr.write(string)
instead of print():
import subprocess
import os
import sys
try:
stdout_string = subprocess.check_output(["bogus"], stderr=subprocess.STDOUT)
sys.stdout.write(stdout_string)
except subprocess.CalledProcessError as cpe:
sys.stderr.write(cpe.returncode)
sys.stderr.write(cpe.output)
except OSError as e:
if e.errno == os.errno.ENOENT:
sys.stderr.write(e.strerror)
else:
# Something else went wrong while trying to run `wget`
sys.stderr.write(e.strerror)
I’d go for:
import distutils.spawn
def is_tool(name):
return distutils.spawn.find_executable(name) is not None
shutil.which
Let me recommend an option that has not been discussed yet: a Python implementation of which
, specifically shutil.which
. It was introduced in Python 3.3 and is cross-platform, supporting Linux, Mac, and Windows. It is also available in Python 2.x via whichcraft. You can also just rip the code for which
right out of whichcraft here and insert it into your program.
def is_tool(name):
"""Check whether `name` is on PATH and marked as executable."""
# from whichcraft import which
from shutil import which
return which(name) is not None
distutils.spawn.find_executable
Another option that has already been mentioned is distutils.spawn.find_executable
.
find_executable
‘s docstring is as follows:
Tries to find ‘executable’ in the directories listed in ‘path’
So if you pay attention, you’ll note that the name of the function is somewhat misleading. Unlike which
, find_executable
does not actually verify that executable
is marked as executable, only that it is on the PATH. So it’s entirely possible (however unlikely) that find_executable
indicates a program is available when it is not.
For example, suppose you have a file /usr/bin/wget
that is not marked executable. Running wget
from the shell will result in the following error: bash: /usr/bin/wget: Permission denied. which('wget') is not None
will return False, yet find_executable('wget') is not None
will return True. You can probably get away with using either function, but this is just something to be aware of with find_executable
.
def is_tool(name):
"""Check whether `name` is on PATH."""
from distutils.spawn import find_executable
return find_executable(name) is not None
I’d change @sorin’s answer as follows, the reason is it would check the name of the program without passing the absolute path of the program
from subprocess import Popen, PIPE
def check_program_exists(name):
p = Popen(['/usr/bin/which', name], stdout=PIPE, stderr=PIPE)
p.communicate()
return p.returncode == 0