91

如何检查 python 脚本中是否存在程序?

假设您要检查是否wget可用curl。我们将假设它们应该在路径中。

最好看到多平台解决方案,但目前,Linux 就足够了。

提示:

  • 运行命令并检查返回代码并不总是足够的,因为即使您尝试,某些工具也会返回非 0 结果--version
  • 检查命令时,屏幕上不应显示任何内容

另外,我希望有一个更通用的解决方案,比如is_tool(name)

4

9 回答 9

177

shutil.which

让我推荐一个尚未讨论过的选项:一个 Python 实现which,特别是shutil.which. 它是在 Python 3.3 中引入的,并且是跨平台的,支持 Linux、Mac 和 Windows。它也可以通过whichcraft在 Python 2.x 中使用。您也可以which直接从 whichcraft 中提取代码并将其插入到您的程序中

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

已经提到的另一个选项是distutils.spawn.find_executable.

find_executable的文档字符串如下:

尝试在“路径”中列出的目录中查找“可执行文件”

因此,如果您注意,您会注意到该函数的名称有些误导。与which,不同的是,find_executable它实际上并没有验证它executable被标记为可执行文件,只是它在 PATH 上。因此,完全有可能(尽管不太可能)find_executable表明程序可用,而实际上它不可用。

例如,假设您有一个/usr/bin/wget未标记为可执行的文件。从 shell运行wget将导致以下错误:bash: /usr/bin/wget: Permission deniedwhich('wget') is not None将返回 False,但find_executable('wget') is not None将返回 True。您可能可以不使用任何一个功能,但这只是使用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
于 2015-12-09T11:12:00.720 回答
50

最简单的方法是尝试使用所需参数运行程序,如果异常不存在则处理:

try:
    subprocess.call(["wget", "your", "parameters", "here"])
except FileNotFoundError:
    # handle file not found error.

这是 Python 中的常见模式:EAFP

在 Python 2 中,您必须改为 catch OsError,因为尚不存在针对 OS 错误的更细粒度的异常类:

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
于 2012-06-26T14:59:03.573 回答
15

您可以使用子进程调用所需的二进制文件:

  • “哪个”:*nix
  • “where”:Win 2003 及更高版本(Xp 有一个插件)

获取可执行路径(假设它在环境路径中)。

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"

或者只是使用 Ned Batchelder 的 wh.py 脚本,这是一个“哪个”跨平台实现:

http://nedbatchelder.com/code/utilities/wh_py.html

于 2012-06-26T15:05:59.077 回答
13
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
于 2012-06-26T15:37:24.693 回答
12

我会去:

import distutils.spawn

def is_tool(name):
  return distutils.spawn.find_executable(name) is not None
于 2015-04-09T12:32:10.697 回答
11

我可能会掏出which wgetwhich curl检查结果是否以您正在使用的程序的名称结尾。unix 的魔力 :)

实际上,您需要做的就是检查which. 所以......使用我们值得信赖的subprocess模块:

import subprocess

rc = subprocess.call(['which', 'wget'])
if rc == 0:
    print 'wget installed!'
else:
    print 'wget missing in path!'

请注意,我使用 cygwin 在 Windows 上对此进行了测试...如果您想弄清楚如何which在纯 python 中实现,我建议您在这里查看:http: //pypi.python.org/pypi/pycoreutils(哦,亲爱的 - 似乎他们不提供which。是时候友好推动了?)

更新:在 Windows 上,您可以使用where代替来which获得类似的效果。

于 2012-06-26T14:58:12.573 回答
5

我将@sorin的答案更改如下,原因是它会检查程序的名称而不传递程序的绝对路径

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
于 2016-09-11T20:29:39.637 回答
1
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
于 2012-06-26T14:59:59.120 回答
0

对@SvenMarnach 的代码稍作修改,解决了打印到标准输出流的问题。如果您使用该subprocess.check_output()函数而不是subprocess.call()then 您可以处理通常打印到代码中标准输出的字符串,并且仍然可以捕获异常和退出状态代码。

如果要抑制终端中的标准输出流,请不要打印从以下返回的标准输出字符串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)

非零退出状态代码和输出字符串在CalledProcessErroras 中提出subprocess.CalledProcessError.returncodesubprocess.CalledProcessError.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)

print()在字符串中添加一个额外的换行符。如果要消除这种情况(并将 std 错误写入 std err 流而不是 std out 流,如上面的 print() 语句所示),请使用sys.stdout.write(string)andsys.stderr.write(string)代替 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)
于 2014-01-07T22:05:56.960 回答