Python子进程命令作为列表而不是字符串

Python子进程命令作为列表而不是字符串,python,subprocess,Python,Subprocess,我需要使用Python中的subprocess模块通过重定向stdout来创建一些新文件。由于存在安全漏洞,我不想使用shell=True 我编写了一些测试命令来解决这个问题,我发现这是可行的: import subprocess as sp filer = open("testFile.txt", 'w') sp.call(["ls", "-lh"], stdout=filer) filer.close() 但是,当我将命令作为一个长字符串而不是列表传递时,它找不到文件。所以当我写这篇文章时

我需要使用Python中的subprocess模块通过重定向stdout来创建一些新文件。由于存在安全漏洞,我不想使用
shell=True

我编写了一些测试命令来解决这个问题,我发现这是可行的:

import subprocess as sp
filer = open("testFile.txt", 'w')
sp.call(["ls", "-lh"], stdout=filer)
filer.close()
但是,当我将命令作为一个长字符串而不是列表传递时,它找不到文件。所以当我写这篇文章时:

import subprocess as sp
filer = open("testFile.txt", 'w')
sp.call("ls -lh", stdout=filer)
filer.close()
我收到了这个错误:

Traceback (most recent call last):
  File "./testSubprocess.py", line 16, in <module>
    sp.call(command2, stdout=filer)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 524, in call
    return Popen(*popenargs, **kwargs).wait()
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 711, in __init__
    errread, errwrite)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 1308, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory
回溯(最近一次呼叫最后一次):
文件“/testSubprocess.py”,第16行,在
sp.call(command2,stdout=filer)
文件“/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py”,第524行,在调用中
返回Popen(*popenargs,**kwargs)。等待()
文件“/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py”,第711行,在__
错误读取,错误写入)
文件“/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py”,第1308行,在执行子进程中
引发子对象异常
OSError:[Errno 2]没有这样的文件或目录

为什么将参数作为字符串或列表传递很重要?

这是因为参数被解释为可执行文件名。如果将
“ls-lh”
放入shell中,则情况相同

luk32:~/projects/tests$ "ls -lh"
bash: ls -lh: command not found
有一个用于此的实用程序称为


但我认为你不需要它。只需假设使用列表是正确的方法,并且该工具支持从不推荐的
shell=True
模式进行转换。

如果希望字符串像在shell中一样进行分割,请使用:

顺便说一句,我在这里的时候,让我说明一下。没有它,例如,如果您添加了无效参数,您将得到空输出。您可能会想知道为什么
文件管理器的输出是空的

with open("testFile.txt", 'w') as filer:
    sp.check_call(shlex.split("ls -lh0"), stdout=filer)
使用
check\u call
您会得到一个错误,该错误会定位问题并阻止后续代码执行:

Traceback (most recent call last):
  File "go.py", line 6, in <module>
    sp.check_call(shlex.split("ls -lh0"), stdout=filer)
  File "/usr/lib/python2.7/subprocess.py", line 540, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['ls', '-lh0']' returned non-zero exit status 2
回溯(最近一次呼叫最后一次):
文件“go.py”,第6行,在
sp.check_调用(shlex.split(“ls-lh0”),stdout=filer)
文件“/usr/lib/python2.7/subprocess.py”,第540行,在check_调用中
引发被调用的进程错误(retcode,cmd)
subprocess.CalledProcessError:命令'['ls','-lh0']'返回非零退出状态2

这是因为通话的方式:

使用
shell=True
,通过shell执行调用,并将命令作为一个字符串发送给shell

使用
shell=False
,通过
execv()
和相关函数直接执行调用。这些函数输出一系列参数

如果只传递一个字符串,则它将被视为仅包含可执行文件名且不带参数的调用的缩写。但是(可能)在您的系统上没有名为ls-lh的可执行文件

确切地说,在
subprocess.py
的深处,会发生以下情况:

        if isinstance(args, types.StringTypes):
            args = [args]
        else:
            args = list(args)
因此,传递的每个字符串都被转换为一个包含一个元素的列表

        if shell:
            args = ["/bin/sh", "-c"] + args
这个我不知道:显然,这允许向被调用的shell传递额外的参数尽管以这种方式记录,但不要使用它,因为它会造成太多混乱

如果
shell=False
,我们将在下面进一步说明

if env is None:
    os.execvp(executable, args)
else:
    os.execvpe(executable, args, env)

它只是获取一个列表并将其用于调用。

根据subprocess.py中的注释:

在UNIX上,shell=False(默认值):在本例中,Popen类 使用os.execvp()执行子程序。args通常应该 这是一个序列。字符串将被视为具有该字符串的序列 作为唯一项(要执行的程序)

在UNIX上,shell=True:如果args是字符串,则指定 要通过shell执行的命令字符串。如果args是一个序列, 第一项指定命令字符串和任何其他项 将被视为附加的shell参数

在Windows上:Popen类使用CreateProcess()执行子进程 对字符串进行操作的程序。如果args是一个序列,它将是 使用list2cmdline方法转换为字符串。请注意 并非所有MS Windows应用程序对命令行的解释都相同 方式:list2cmdline是为使用相同 规则作为MS C运行时


在UNIX子进程中,调用('ls-l')将失败,而在Windows中它将成功。导致问题的是os.execvp()。问题是整个字符串作为参数传递。如果您执行subprocess.call('free'),它将在UNIX中成功。

一个小贴士:不要同时提示列表参数和
shell=True
的可能性——这是一个常见的错误,它甚至出现在
subprocess
模块的PyMOTW页面中。@J.F.Sebastian您是对的,但我对发生的事情非常感兴趣,因此对它进行了研究。Python bug tracker上存在一个问题:。吸取了教训,新的API(如
asyncio
)使用了两个不同的名称
        if shell:
            args = ["/bin/sh", "-c"] + args
if env is None:
    os.execvp(executable, args)
else:
    os.execvpe(executable, args, env)