测试Python中是否存在可执行文件?


297

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

简单来说,我的意思是像which命令这样的东西,将是完美的。我不想手动搜索PATH或涉及尝试使用Popen&al 执行它的操作,以查看它是否失败(这就是我现在正在做的,但是可以想象是launchmissiles


4
搜索PATH环境变量有什么问题?您认为UNIX的“哪个”命令做什么?
杰伊

1
stdlib中的which.py​​脚本是一种简单的方法吗?
jfs

@JF-which.py​​脚本包括 使用Python取决于'ls',其他一些注释表明Piotr正在寻找跨平台的答案。
杰伊

@杰伊:感谢您的评论。我在Windows上安装了coreutils,所以我没有注意到which.py​​是特定于Unix的。
jfs

Answers:


321

我想到的最简单的方法:

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

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

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

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


谢谢杰伊,我接受你的回答,尽管对我来说,它的回答是负面的。库中不存在这样的函数,我只需要编写它(我承认我的表述不够清楚,因为我知道它的作用)。
Piotr Lesnicki

1
杰伊,如果您根据我的回答来完成(具有完整的“ w”),那么我可以删除我的回答。
Piotr Lesnicki

2
对于某些操作系统,您可能需要添加可执行文件的扩展名。例如,在Ubuntu上,我可以编写which(“ scp”),但在Windows上,我需要编写which(“ scp.exe”)。
waffleman 2010年

13
我建议将“ os.path.exists”更改为“ os.path.isfile”。否则,在Unix中,这可能会错误地将设置了+ x位的目录与之匹配。我还发现将其添加到函数顶部很有用:import sys; 如果sys.platform ==“ win32”而不是program.endswith(“。exe”):程序+ =“ .exe”。在Windows下,可以像在cmd窗口中一样引用“ calc”或“ calc.exe”。
凯文·伊瓦尔森

1
@KevinIvarsen更好的选择是遍历PATHEXTenv var 的值,因为它与vs command一样有效command.comscriptscript.bat
Lekensteyn 2011年

325

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

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

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


7
在上win32,该distutils.spawn.find_executable实现仅查找.exe而不使用扩展列表搜索in中的set %PATHEXT%。那不是很好,但是它可能适用于某人需要的所有情况。
rakslice

7
用法示例:from distutils import spawn php_path = spawn.find_executable("php")
codefreak 2013年

6
显然distutils.spawn无法可靠地获得:在OS X 10.10上安装Python 2.7.6的系统安装(/ usr / bin / python)后,我得到:AttributeError: 'module' object has no attribute 'spawn',虽然很奇怪,它可以在具有相同版本Python的同一台机器上运行,但是从virtualenv安装。
Josh Kupershmidt

8
@JoshKupershmidt,请确保import distutils.spawn或遵循from distutils import spawn语法,而不仅仅是import distutils。否则,它可能无法访问,AttributeError即使有,您也将获得以上内容。
约翰·圣约翰


39

对于python 3.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's Answer的一线书,在这里也作为lambda函数:

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')

最后,缩进为一个函数:

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及更高版本:

import shutil

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

作为Jan-Philip Gehrcke的单人回答

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

作为def:

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

1
“缩进为函数”版本x在应使用的变量处使用变量cmd
2015年

您还必须添加测试以查看是否os.path.join(path, cmd)为文件,不是吗?毕竟,目录也可以设置可执行位...
MestreLion

@MestreLion听起来像是可能的情况,您介意确认此行为并更新此答案吗?如果有帮助,我很乐意将此帖子更改为社区Wiki。
ThorSummoner

1
@ThorSummoner:我已经确认了,确实确实需要对文件进行测试。一个简单的测试: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/dummy
MestreLion

1
and os.path.isfile(...)在适当的位置添加简单的内容即可解决此问题
MestreLion

19

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

OTOH,为什么还要打扰搜索可执行文件?操作系统将在popen调用过程中为您执行此操作,并且如果找不到可执行文件,则会引发异常。您需要做的就是捕获给定OS的正确异常。请注意,在Windows上,subprocess.Popen(exe, shell=True)如果exe找不到,它将以静默方式失败。


结合PATHEXT到上述实施which(在周杰伦的回答):

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

1
它修复了接受的答案中的一个错误,感觉这个答案应该放在最前面。
NiTe Luo

yieldin的巧妙用法ext_candidates,使我对关键字的工作原理有了更好的了解
Grant Humphries

15

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

这似乎为我工作:

借助Mestreion,经过编辑可在Linux上工作

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

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

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

用法示例:

>>> cmd_exists("jsmin")
True
>>> cmd_exists("cssmin")
False
>>> cmd_exists("ls")
True
>>> cmd_exists("dir")
False
>>> cmd_exists("node")
True
>>> cmd_exists("steam")
False

2
确定这有效吗?这是一种非常好的方法,但是type它是内置的Shell,而不是可执行文件,因此subprocess.call()在这里失败。
MestreLion

1
您尝试过还是只是在理论化?无论如何,它可以在我的Mac上运行。
哈森

我在Ubuntu 12.04中尝试过,它抛出OSError: [Errno 2] No such file or directory。也许在Mac中type是实际的命令
MestreLion,

2
经过一个LOT的测试,我发现如何解决:添加shell=True和替换["type", cmd]"type " + cmd
MestreLion

4
注意:确保变量“ cmd”包含有效数据。如果来自外部来源,那么坏人可能会给您“ ls; rm -rf /”。我认为Python中的解决方案(没有子进程)要好得多。下一步:如果经常调用此方法,则子流程解决方案要慢得多,因为需要生成许多流程。
guettli

7

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

操作系统X_OK

包含在access()的mode参数中的值,以确定是否可以执行路径。

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


谢谢,吉梅尔,所以基本上我有我的答案:不存在这样的功能,我必须手动执行。
Piotr Lesnicki

不要使用os.access。访问功能是为suid程序设计的。
Changming Sun

6

基于宽恕要比许可容易,我只会尝试使用它并捕获错误(在这种情况下为OSError-我检查文件不存在并且文件不可执行,并且两者都给出OSError)。

如果可执行文件具有诸如--version快速禁止操作的标志之类的功能,则将有所帮助。

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

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


3
即使调用--version名为launchmissiles!的程序也太危险了。
xApple

1
+1,我喜欢这种方法。EAFP是Python的黄金法则。除了可能要设置用户界面之外,为什么还要知道是否launchmissies存在,除非您想发射导弹?更好地执行它并根据退出状态/异常采取行动
MestreLion

这种方法的问题是将输出打印到控制台。如果使用管道并且shell = True,则OSError永远不会引发
Nick Humrich 2014年

在macOS上,您还具有存根可执行文件,例如git,您可能不想盲目运行。
Bob Aman

5

我知道我在这里有点死灵法师,但是我偶然发现了这个问题,并且公认的解决方案并不能在所有情况下对我都有效。特别是,“可执行”模式检测以及提供文件扩展名的要求。此外,python3.3 shutil.which(使用PATHEXT)和python2.4 + distutils.spawn.find_executable(仅尝试添加'.exe')都仅在部分情况下有效。

因此,我写了一个“超级”版本(基于公认的答案和PATHEXT来自Suraj 的建议)。此版本which的任务会更彻底地完成任务,并首先尝试一系列“广域”广度优先技术,并最终在该PATH空间上尝试更细粒度的搜索:

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

用法如下所示:

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

接受的解决方案并没有为我工作在这种情况下,因为有文件,如meld.1meld.icomeld.doap,等也在目录中,其中一个被退回,而不是(大概是因为字典序第一),因为在接受答案的可执行测试是不完整的,并给予误报。



2

我在StackOverflow中找到了可以解决我问题的方法。如果可执行文件具有一个选项(例如--help或--version),该选项可以输出某些内容并返回退出状态为零,则此方法有效。请参见抑制对可执行文件的Python调用中的输出 -如果可执行文件在路径中,则此答案中代码段末尾的“结果”将为零,否则很可能为1。


2

这似乎很简单,并且可以在python 2和3中使用

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

对不起,Jaap,但是此解决方案仅在可执行文件未正确调用(如果调用错误)时不起作用。因此,例如,它适用于“ dir”和“ ls”,但是如果针对需要配置的内容执行,即使可执行文件存在,它也会中断。
2015年

1
您所说的“需要配置”到底是什么意思?就其本身而言,“哪个”实际上不执行任何操作,而只是检查PATH是否存在使用该名称(man which)的可执行文件。
jaap 2015年

1
哦,所以您要使用“哪个”来查找可执行文件。所以这仅适用于Linux / Unix吗?
Spedge 2015年

1
使用command -v executabletype executable通用。在某些情况下,在Mac上无法返回预期结果。
RJ

1

一个重要的问题是“ 为什么需要测试可执行文件是否存在?” 也许你不知道吗?;-)

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

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

更新:我对os.startfile可移植性是错误的...仅Windows。在Mac上,您必须运行open命令。而xdg_open在Unix上。添加对Mac和Unix的支持时,存在一个Python问题os.startfile


1

您可以尝试名为“ 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

1

添加了Windows支持

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

0

您可以判断os模块是否存在文件。考虑到很多事情是在Windows上无法运行的nix上执行的,尤其是一个可执行文件似乎非常不可移植,反之亦然。


0

似乎很明显的选择是“哪个”,它通过popen解析结果,但是您可以使用os类来模拟它。在伪python中,它看起来像这样:

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

对于使用os.exec或类似的命令运行“哪个”命令,我会非常小心。不仅它通常很慢(如果要关注性能),而且如果您将变量用作exec字符串的一部分,那么安全就成为一个问题。有人可以潜入“ rm -rf /”。
帕拉帕

1
由于我们将使用os.popen函数来运行程序创建的命令,因此哪一个实际上不适用,不是吗?
查理·马丁

2
谢谢,但我不确定Windows等系统中是否存在“哪个”。我本质上想知道标准库中是否存在某些奇特的东西
Piotr Lesnicki

在标准Windows安装中,仍然没有which命令。有一个UnxUtils版本,但是您必须知道/指定扩展名,否则将找不到该程序。
Tobias

0

因此,基本上,您想在已挂载的文件系统中找到一个文件(不一定仅在PATH目录中),然后检查它是否可执行。这转化为以下计划:

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

我想说,以便携式方式进行此操作将需要大量的计算能力和时间。真的是您需要的吗?


0

在标准Python发行版中(例如Windows上),有一个which.py脚本'\PythonXX\Tools\Scripts\which.py'

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


0

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

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


-3

使用python Fabric库:

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

2
这是一个非常糟糕的建议。实际上,您是在使程序依赖于远程执行库来生成本地程序(Python stdlib可以轻松实现此功能),此外,您还取决于which(1)所有系统上都不存在该程序。
米哈尔戈尔诺-
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.