测试 Python 中是不是存在可执行文件?
Posted
技术标签:
【中文标题】测试 Python 中是不是存在可执行文件?【英文标题】:Test if executable exists in Python?测试 Python 中是否存在可执行文件? 【发布时间】:2010-09-27 11:33:15 【问题描述】:在 Python 中,是否有一种可移植且简单的方法来测试可执行程序是否存在?
简单地说,我的意思是类似于which
命令,这将是完美的。我不想手动搜索 PATH 或尝试使用 Popen
& al 执行它并查看它是否失败(这就是我现在正在做的事情,但想象它是 launchmissiles
)
【问题讨论】:
搜索PATH环境变量有什么问题?你认为 UNIX 的“which”命令有什么作用? stdlib 中的 which.py 脚本是一种简单的方法吗? @J.F. - which.py 脚本包括。使用 Python 取决于“ls”,而其他一些 cmets 表明 Piotr 正在寻找跨平台的答案。 @Jay:感谢您的评论。我在 Windows 上安装了 coreutils,所以我没有注意到 which.py 是特定于 unix 的。 也许 ***.com/a/13936916/145400 应该是 2020 年公认的答案? :) 【参考方案1】:我知道这是一个古老的问题,但您可以使用distutils.spawn.find_executable
。这一直是documented since python 2.4,并且自 python 1.6 以来就存在。
import distutils.spawn
distutils.spawn.find_executable("notepad.exe")
另外,Python 3.3 现在提供shutil.which()
。
【讨论】:
在win32
上,distutils.spawn.find_executable
实现仅查找.exe
,而不是使用扩展列表来搜索%PATHEXT%
中的集合。这不是很好,但它可能适用于某人需要的所有情况。
用法示例:from distutils import spawn
php_path = spawn.find_executable("php")
显然 distutils.spawn
不可靠:在 OS X 10.10 上使用我的 Python 2.7.6 系统安装 (/usr/bin/python),我得到: AttributeError: 'module' object has no attribute 'spawn'
,虽然很奇怪在同一台机器上使用相同版本的 Python,但来自 virtualenv 安装。
@JoshKupershmidt,确保使用import distutils.spawn
,或遵循from distutils import spawn
语法而不仅仅是import distutils
。否则它可能无法访问,即使它在那里,你也会得到上面的AttributeError
。
记录在案:“Python 3.3 现在提供 shutil.which()”是在 2013 年 1 月 27 日从***.com/a/13936916/145400 粗暴地复制/粘贴而没有署名的。【参考方案2】:
我能想到的最简单的方法:
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' 命令的行为。
编辑:更新为每个 cmets 使用 os.path.isfile() 而不是 os.path.exists()。
编辑:path.strip('"')
似乎在这里做错了事。 Windows 和 POSIX 似乎都不鼓励引用 PATH 项。
【讨论】:
谢谢杰,我接受你的回答,虽然对我来说它回答了我的问题是否定的。库中不存在这样的函数,我只需要编写它(我承认我的表述不够清楚,因为我知道它是做什么的)。 Jay,如果你按照我的完成你的答案(有完整的'w'),我可以删除我的。 对于某些操作系统,您可能需要添加可执行文件的扩展名。例如,在 Ubuntu 上我可以写 which("scp") 但在 Windows 上,我需要写 which("scp.exe")。 我建议将“os.path.exists”更改为“os.path.isfile”。否则在 Unix 中,这可能会错误地匹配设置了 +x 位的目录。我还发现将其添加到函数顶部很有用:import sys;如果 sys.platform == "win32" 而不是 program.endswith(".exe"): program += ".exe"。这样,在 Windows 下,您可以引用“calc”或“calc.exe”,就像在 cmd 窗口中一样。 @KevinIvarsen 更好的选择是循环遍历PATHEXT
env var 的值,因为 command
与 command.com
一样有效,script
与 script.bat
一样有效【参考方案3】:
使用 Python 出色的标准库中的 shutil.which()。 包括电池!
【讨论】:
【参考方案4】:对于 python 3.3 及更高版本:
import shutil
command = 'ls'
shutil.which(command) is not None
作为Jan-Philip Gehrcke Answer的单行者:
cmd_exists = lambda x: shutil.which(x) is not None
作为一个定义:
def cmd_exists(cmd):
return shutil.which(cmd) is not None
对于 python 3.2 及更早版本:
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)
)
这是Jay's Answer 的单行代码,这里也是一个 lambda 函数:
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')
或者最后,作为函数缩进:
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
【讨论】:
“作为函数缩进”版本使用变量x
,它应该是cmd
您还必须添加一个测试以查看os.path.join(path, cmd)
是否为文件,不是吗?毕竟,目录也可以设置可执行位...
@MestreLion 这听起来像是一个可能的情况,您介意确认此行为并更新此答案吗?如果有帮助,我很乐意将此帖子更改为社区 wiki。
@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
在适当的地方添加一个简单的and os.path.isfile(...)
就足以解决这个问题【参考方案5】:
只需记住在 Windows 上指定文件扩展名即可。否则,您必须使用PATHEXT
环境变量为Windows 编写一个非常复杂的is_exe
。您可能只想使用FindPath。
OTOH,您为什么还要费心搜索可执行文件?操作系统将作为popen
调用的一部分为您执行此操作,如果找不到可执行文件,则会引发异常。您需要做的就是捕捉给定操作系统的正确异常。请注意,在 Windows 上,如果找不到 exe
,subprocess.Popen(exe, shell=True)
将静默失败。
将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
【讨论】:
它修复了接受的答案中的一个错误,感觉这个答案应该放在首位。 在ext_candidates
中巧妙地使用yield
,让我更好地理解了该关键字的工作原理【参考方案6】:
对于 *nix 平台(Linux 和 OS X)
这似乎对我有用:
编辑可以在 Linux 上工作,感谢Mestreion
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
【讨论】:
我已经在 Ubuntu 12.04 中尝试过,它会抛出OSError: [Errno 2] No such file or directory
。也许在 Mac 中 type
是一个实际的命令
使用shell=True
在Linux 中是强制性的,因为type
是一个内置的shell(我想知道为什么它不在Mac 中)。但是,正如文档所指出的,将它与用户提供的任意字符串(例如 cmd
)一起使用是一个主要的安全风险
酷,感谢您修复它。我编辑了答案以包含您的修复。以后可以随意编辑错误的答案,让它们更正确!
很高兴知道,谢谢,但仅供参考:这在 tcsh (6.17.00) 上不起作用,因为 type 不是 tcsh 或 csh 中的内置函数。所以它不是很普遍。
注意:确保变量“cmd”包含有效数据。如果它来自外部来源,坏人可能会给你“ls; rm -rf /”。我认为 in-python 解决方案(没有子进程)要好得多。下一点:如果你经常调用这个方法,子进程的解决方案会慢很多,因为需要生成很多进程。【参考方案7】:
基于它是easier to ask forgiveness than permission(并且,重要的是,该命令可以安全运行)我会尝试使用它并捕获错误(在这种情况下为 OSError - 我检查了文件不存在并且文件不可执行,它们都给出 OSError)。
如果可执行文件有类似 --version
或 --help
的标志,这是一个快速无操作的标志。
import subprocess
myexec = "python2.8"
try:
subprocess.call([myexec, '--version']
except OSError:
print "%s not found on path" % myexec
这不是一个通用的解决方案,但对于许多用例来说是最简单的方法 - 那些代码需要寻找一个可以安全运行或至少可以安全运行的已知可执行文件的用例给定标志。
【讨论】:
在名为launchmissiles
的程序上调用--version
也太危险了!
+1,我喜欢这种方法。 EAFP 是一条黄金 Python 规则。除了设置 UI 之外,你为什么想知道launchmissies
是否存在,除非你想发射导弹?最好执行它并根据退出状态/异常采取行动
这种方法的问题是输出打印到控制台。如果您使用管道和 shell=True,则 OSError 永远不会引发
在 macOS 上,您还具有存根可执行文件,例如git
你可能不想瞎跑。
@MestreLion 好吧,您可能只想检查launchmissiles
是否真的存在,这样您就可以在以后需要时立即launchmissiles
,而不是想弄清楚如何安装@ 987654331@在紧急情况下。【参考方案8】:
请参阅os.path 模块以了解有关路径名的一些有用功能。要检查现有文件是否可执行,请使用 os.access(path, mode) 和 os.X_OK 模式。
os.X_OK
要包含在 access() 的模式参数中的值,以确定路径是否可以执行。
编辑:建议的 which()
实现缺少一条线索 - 使用 os.path.join()
构建完整文件名。
【讨论】:
谢谢,gimel,所以基本上我有我的答案:不存在这样的功能,我必须手动完成。 不要使用 os.access。访问功能是为suid程序设计的。【参考方案9】:我知道我在这里有点像死灵法师,但我偶然发现了这个问题,并且接受的解决方案不适用于所有情况我认为无论如何提交可能很有用。特别是“可执行”模式检测,以及提供文件扩展名的要求。此外,python3.3 的shutil.which
(使用PATHEXT
)和python2.4+ 的distutils.spawn.find_executable
(只是尝试添加'.exe'
)都只适用于部分情况。
所以我写了一个“超级”版本(基于接受的答案,以及来自 Suraj 的 PATHEXT
建议)。这个版本的which
更彻底地完成了任务,首先尝试了一系列“broadphase”广度优先技术,最终在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.1
、meld.ico
、meld.doap
等文件,其中一个被返回(大概是从字典顺序开始)因为已接受答案中的可执行测试不完整并给出误报。
【讨论】:
很多机会让已安装的驱动器不区分大小写 - 即使 /tmp 不区分大小写。并且写一个文件不是一个好的机制......而是LS每个路径成员的根......然后检查是否存在一个已知的条目,但存在不同的缺失案例。现在,即使是只读安装的网络驱动器(通常用于发布软件),也可以使用!【参考方案10】:添加窗口支持
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
【讨论】:
【参考方案11】:一个重要的问题是“为什么需要测试可执行文件是否存在?”也许你不知道? ;-)
最近我需要这个功能来启动 PNG 文件的查看器。我想遍历一些预定义的查看器并运行第一个存在的查看器。幸运的是,我遇到了os.startfile
。好多了!简单、便携并使用系统上的默认查看器:
>>> os.startfile('yourfile.png')
更新:我错了 os.startfile
是可移植的……它仅适用于 Windows。在 Mac 上,您必须运行 open
命令。和 Unix 上的 xdg_open
。有一个 Python issue 为 os.startfile
添加 Mac 和 Unix 支持。
【讨论】:
【参考方案12】:这看起来很简单,适用于 python 2 和 3
try: subprocess.check_output('which executable',shell=True)
except: sys.exit('ERROR: executable not found')
【讨论】:
抱歉,Jaap,但此解决方案仅在可执行文件未调用退出代码 1(如果调用不正确)时有效。因此,例如,它适用于“dir”和“ls”,但如果你针对需要配置的东西执行它,即使可执行文件在那里,它也会中断。 “需要配置”到底是什么意思? 'which' 本身实际上并没有执行任何操作,只是检查 PATH 是否存在同名的可执行文件(man which)。 哦,所以您使用“which”来查找可执行文件。所以这只适用于 Linux/Unix? 使用command -v executable
或type executable
是通用的。在某些情况下,在 Mac 上不会返回预期结果。【参考方案13】:
您可以尝试名为“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
【讨论】:
【参考方案14】:所以基本上你想在挂载的文件系统中找到一个文件(不一定只在 PATH 目录中)并检查它是否是可执行的。这转化为以下计划:
枚举本地挂载文件系统中的所有文件 匹配结果与名称模式 为找到的每个文件检查它是否可执行我想说,以便携式方式执行此操作将需要大量计算能力和时间。真的是你需要的吗?
【讨论】:
【参考方案15】:标准 Python 发行版中有一个 which.py 脚本(例如,在 Windows 上 '\PythonXX\Tools\Scripts\which.py'
)。
编辑:which.py
依赖于ls
,因此它不是跨平台的。
【讨论】:
以上是关于测试 Python 中是不是存在可执行文件?的主要内容,如果未能解决你的问题,请参考以下文章