os.system 确实会启动一个二进制文件,该二进制文件在路径中包含空格,方法是将该路径包含在引号中。 (如果您习惯于使用终端,这应该是一个非常明显的解决方案。)然而,它本身并不能解决这个功能更痛苦的问题......一旦你这样做了,你就可以运行为您的命令添加参数时遇到麻烦! (啊!)
所有当前的建议是现在使用subprocess 模块,而不是这个旧的、不受欢迎的功能。也可以使用shlx 将平面字符串转换为那些子进程函数的列表。我也遇到了这些方法的问题或痛苦,我不会漫不经心地谈论......而且,有时使用os.system 会更容易,因为你想要的只是外壳上的薄包装,隐式显示输出在控制台上流式传输,同步工作等。我当然希望有一个内置函数可以像这样在 shell 上执行命令,绝对 0 解析、包装、抽象... p>
由于没有没有“过滤器”的内置,这是我的os.system 解决方案补丁。这是从我的一个开源库中提取的。这已经在 Windows、Mac 和 Ubuntu Linux 上进行了测试。我知道这不是 100% 万无一失的,而且它比人们想的要复杂得多,但也不算太糟糕。
当您调用此 _system() 包装器(传递要执行的字符串)时,只需将您的长路径用引号括起来,并包括任何需要或不带引号的参数(即与您在终端中输入命令完全相同) !)。在命令中的第一个“令牌”上,这将删除 Mac 或 Linux 路径中的引号和转义空格。在 Windows 上,它通过实际解析给定环境中的内容来使用“短名称”。这部分代码有点棘手。基本上,它使用批处理机制进行名称解析,并将结果通过 stderr 发送回,目的是解析您在 stdout 上获得的 Popen() 结果。
我想我包含了您需要的所有导入和定义。如果我遗漏了任何内容(复制和粘贴源代码尖峰),请告诉我。
from os import system, getcwd, chdir
from subprocess import Popen, PIPE
import platform
__plat = platform.system()
IS_WINDOWS = __plat == "Windows"
IS_LINUX = __plat == "Linux"
IS_MACOS = __plat == "Darwin"
__SCRUB_CMD_TMPL = "{0}{1}"
__DBL_QUOTE = '"'
__SPACE = ' '
__ESC_SPACE = '\\ '
if IS_WINDOWS :
__BATCH_RUN_AND_RETURN_CMD = ["cmd","/K"] # simply assuming cmd is on the system path...
__BATCH_ONE_LINER_TMPLT = "{0} 1>&2\n" # the newline triggers execution when piped in via stdin
__BATCH_ESCAPE_PATH_TMPLT = 'for %A in ("{0}") do @echo %~sA'
from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
__BATCH_ONE_LINER_STARTUPINFO = STARTUPINFO()
__BATCH_ONE_LINER_STARTUPINFO.dwFlags |= STARTF_USESHOWWINDOW
def _system( cmd, wrkDir=None ):
if wrkDir is not None:
initWrkDir = getcwd()
print( 'cd "%s"' % (wrkDir,) )
chdir( wrkDir )
cmd = __scrubSystemCmd( cmd )
print( cmd )
system( cmd )
print('')
if wrkDir is not None: chdir( initWrkDir )
def __scrubSystemCmd( cmd ):
"""
os.system is more convenient than the newer subprocess functions
when the intention is to act as very thin wrapper over the shell.
There is just one MAJOR problem with it:
If the first character in the command is a quote (to escape a long path
to the binary you are executing), then the limited (undesirable) parsing
built into the function can all fall apart. So, this scrub function
solves that...
"""
if not cmd.startswith( __DBL_QUOTE ): return cmd
cmdParts = cmd[1:].split( __DBL_QUOTE )
safeBinPath = _escapePath( cmdParts[0] )
args = __DBL_QUOTE.join( cmdParts[1:] ) # (the leading space will remain)
return __SCRUB_CMD_TMPL.format( safeBinPath, args )
def _escapePath( path ):
if not IS_WINDOWS: return path.replace(__SPACE, __ESC_SPACE)
return( path if __SPACE not in path else
__batchOneLinerOutput( __BATCH_ESCAPE_PATH_TMPLT.format(path) ) )
def __batchOneLinerOutput( batch ):
cmd = __BATCH_ONE_LINER_TMPLT.format( batch )
p = Popen( __BATCH_RUN_AND_RETURN_CMD, shell=False,
startupinfo=__BATCH_ONE_LINER_STARTUPINFO,
stdin=PIPE, stdout=PIPE, stderr=PIPE )
# pipe cmd to stdin, return stderr, minus a trailing newline
return p.communicate( cmd )[1].rstrip()
更新
我最近想到了一个更好的 Windows 上下文技巧。您不需要任何转换为短文件名或空格的转义序列。您需要做的就是通过检查命令中的第一个字符是否是双引号来阻止 Python 源代码。好吧,在 Windows cmd / Batch 上,您可以在任何命令前加上 @ 以指示不要“回显”该行。所以,如果你只是在你的命令前面加上它,将不再有前导引号!您可能也不希望命令回显,因此无论如何它都是对自身的改进。
基本上,将上面的__scrubSystemCmd 替换为以下内容。如果您愿意,您可以删除所有获得短文件名的代码(这就像我最初发布的代码的一半!)...
if IS_WINDOWS:
...
__NO_ECHO_PREFIX = "@"
def __scrubSystemCmd( cmd ):
if IS_WINDOWS:
if not cmd.startswith( __NO_ECHO_PREFIX ):
return __NO_ECHO_PREFIX + cmd
elif not cmd.startswith( __DBL_QUOTE ): return cmd
cmdParts = cmd[1:].split( __DBL_QUOTE )
safeBinPath = _escapePath( cmdParts[0] )
args = __DBL_QUOTE.join( cmdParts[1:] ) # (the leading space will remain)
return __SCRUB_CMD_TMPL.format( safeBinPath, args )