关于路径:测试Python中是否存在可执行文件?

Test if executable exists in Python?

在Python中,是否有一种可移植且简单的方法来测试可执行程序是否存在?

简单来说,我的意思是像which这样的命令,这将是完美的。 我不想手动搜索PATH或者尝试使用Popen& 并看看它是否失败(这就是我现在正在做的,但想象它是launchmissiles)


我能想到的最简单方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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["PATH"].split(os.pathsep):
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file

    return None

编辑:更新的代码示例包括用于处理案例的逻辑,其中提供的参数已经是可执行文件的完整路径,即"which / bin / ls"。这模仿了UNIX'which'命令的行为。

编辑:更新为每个注释使用os.path.isfile()而不是os.path.exists()。

编辑:path.strip('"')在这里做错了。 Windows和POSIX似乎都不鼓励引用引用的PATH项目。


我知道这是一个古老的问题,但你可以使用distutils.spawn.find_executable。这是自python 2.4以来已经记录的,并且自python 1.6以来就已存在。

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

此外,Python 3.3现在提供shutil.which()


Python 3.3现在提供shutil.which()。


对于python 3.2及更早版本:

1
2
my_command = 'ls'
any(os.access(os.path.join(path, my_command), os.X_OK) for path in os.environ["PATH"].split(os.pathsep))

这是Jay的答案,也是一个lambda函数:

1
2
cmd_exists = lambda x: any(os.access(os.path.join(path, x), os.X_OK) for path in os.environ["PATH"].split(os.pathsep))
cmd_exists('ls')

或者最后,缩进为一个函数:

1
2
3
4
5
def cmd_exists(cmd):
    return any(
        os.access(os.path.join(path, cmd), os.X_OK)
        for path in os.environ["PATH"].split(os.pathsep)
    )

对于python 3.3及更高版本:

1
2
3
4
import shutil

command = 'ls'
shutil.which(command) is not None

作为Jan-Philip Gehrcke的单行答案:

1
cmd_exists = lambda x: shutil.which(x) is not None

作为def:

1
2
def cmd_exists(cmd):
    return shutil.which(cmd) is not None


只需记住在Windows上指定文件扩展名即可。否则,您必须使用PATHEXT环境变量为Windows编写一个非常复杂的is_exe。您可能只想使用FindPath。

OTOH,你为什么还要费心去搜索可执行文件呢?操作系统将作为popen call& amp;的一部分为您完成。如果找不到可执行文件,将引发异常。您需要做的就是捕获给定操作系统的正确异常。请注意,在Windows上,如果找不到exesubprocess.Popen(exe, shell=True)将无提示失败。

PATHEXT合并到which的上述实现中(在Jay的答案中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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


对于* nix平台(Linux和OS X)

这似乎对我有用:

由于Mestreion,我被编辑在Linux上工作

1
2
3
def cmd_exists(cmd):
    return subprocess.call("type" + cmd, shell=True,
        stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0

我们在这里做的是使用内置命令type并检查退出代码。如果没有这样的命令,type将以1退出(或者无论如何都是非零状态代码)。

关于stdout和stderr的一点只是为了使type命令的输出静音,因为我们只对退出状态代码感兴趣。

用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
>>> cmd_exists("jsmin")
True
>>> cmd_exists("cssmin")
False
>>> cmd_exists("ls")
True
>>> cmd_exists("dir")
False
>>> cmd_exists("node")
True
>>> cmd_exists("steam")
False


有关路径名的一些有用功能,请参阅os.path模块。要检查现有文件是否可执行,请使用os.access(path,mode)和os.X_OK模式。

os.X_OK

Value to include in the mode parameter of access() to determine if path can be executed.

编辑:建议的which()实现缺少一个线索 - 使用os.path.join()来构建完整的文件名。


在请求宽恕比允许更容易的基础上,我会尝试使用它并捕获错误(在这种情况下OSError - 我检查文件不存在,文件不可执行,它们都给出了OSError)。

如果可执行文件具有类似--version标志的快速无操作,则会有所帮助。

1
2
3
4
5
6
import subprocess
myexec ="python2.8"
try:
    subprocess.call([myexec, '--version']
except OSError:
    print"%s not found on path" % myexec

这不是一般解决方案,但对于许多用例来说是最简单的方法 - 代码需要查找一个众所周知的可执行文件。


我知道我在这里是一个死灵法师,但我偶然发现了这个问题,并且所接受的解决方案对我来说并不适用于所有情况。无论如何,提交它可能是有用的。特别是,"可执行"模式检测,以及提供文件扩展名的要求。此外,python3.3的shutil.which(使用PATHEXT)和python2.4 +的distutils.spawn.find_executable(只是尝试添加'.exe')仅适用于案例的子集。

所以我写了一个"超级"版本(基于接受的答案,以及来自Suraj的PATHEXT建议)。此版本的which更彻底地执行任务,并首先尝试一系列"广义"广度优先技术,并最终在PATH空间上尝试更细粒度的搜索:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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

用法如下:

1
2
>>> which.which("meld")
'C:\\Program Files (x86)\\Meld\\meld\\meld.exe'

在这种情况下,接受的解决方案对我来说不起作用,因为在目录中也有像meld.1meld.icomeld.doap等文件,其中一个被返回(大概是从字典顺序开始)因为可执行文件在接受的答案中测试是不完整的,并给出误报。


最好的例子应该是Python 3中的python bulit-in模块shutil.which()。链接是https://hg.python.org/cpython/file/default/Lib/shutil.py


这看起来很简单,并且在python 2和3中都有效

1
2
try: subprocess.check_output('which executable',shell=True)
except: sys.exit('ERROR: executable not found')


我在StackOverflow中找到了解决问题的方法。这项工作提供了可执行文件有一个选项(如--help或--version)输出的东西,并返回退出状态为零。请参阅Python可执行文件调用中的抑制输出 - 如果可执行文件位于路径中,则此答案中代码段末尾的"结果"将为零,否则最有可能为1。


一个重要的问题是"为什么需要测试可执行文件是否存在?"也许你没有? ;-)

最近我需要此功能来启动PNG文件的查看器。我想迭代一些预定义的查看器并运行第一个存在的查看器。幸运的是,我遇到了os.startfile。它好多了!简单,便携并使用系统上的默认查看器:

1
>>> os.startfile('yourfile.png')

更新:我错误地认为os.startfile是可移植的...它只是Windows。在Mac上,您必须运行open命令。在Unix上xdg_open。在为os.startfile添加Mac和Unix支持时存在Python问题。


您可以尝试名为"sh"的外部库(http://amoffat.github.io/sh/)。

1
2
3
import sh
print sh.which('ls')  # prints '/bin/ls' depending on your setup
print sh.which('xxx') # prints None


添加了Windows支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

你可以判断一下os模块是否存在一个文件。考虑到很多东西都可以在nix上执行而不是在windows上,反之亦然,特别是一个可执行文件似乎非常不可移植。


以前的示例都不适用于所有平台。通常它们无法在Windows上运行,因为您可以在没有文件扩展名的情况下执行,并且可以注册新的扩展名。例如在Windows上,如果python安装得很好,它就足以执行'file.py'并且它会起作用。

我唯一有效且可移植的解决方案是执行命令并查看错误代码。任何体面的可执行文件都应该有一组无效的调用参数。


标准Python发行版中有一个which.py?疟荆ɡ纾赪indows '\PythonXX\Tools\Scripts\which.py'上)。

编辑:which.py取决于ls因此它不是跨平台的。


所以基本上你想在挂载的文件系统中找到一个文件(不一定只在PATH目录中)并检查它是否可执行。这转换为以下计划:

  • 枚举本地安装的文件系统中的所有文件
  • 匹配结果与名称模式
  • 找到每个文件检查它是否可执行

我会说,以便携方式执行此操作需要大量的计算能力和时间。这真的是你需要的吗?


似乎显而易见的选择是"哪个",通过popen解析结果,但你可以使用os类来模拟它。在pseudopython中,它看起来像这样:

1
2
3
4
for each element r in path:
    for each file f in directory p:
        if f is executable:
           return True


使用python结构库:

1
2
3
4
5
6
7
8
9
10
11
12
13
from fabric.api import *

def test_cli_exists():
   """
    Make sure executable exists on the system path.
   """

    with settings(warn_only=True):
        which = local('which command', capture=True)

    if not which:
        print"command does not exist"

    assert which