Python Subprocess命令作为List Not String

时间:2014-05-07 14:35:10

标签: python subprocess

我需要使用Python中的子进程模块通过重定向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

如果我将参数作为字符串或列表传递,为什么重要?

4 个答案:

答案 0 :(得分:5)

如果您希望像在shell中一样分割字符串,请使用shlex

import subprocess as sp
import shlex
with open("testFile.txt", 'w') as filer:
    sp.call(shlex.split("ls -lh"), stdout=filer)

顺便说一句,让我在check_call提出这个问题。没有它,例如,如果添加了无效参数,则会得到空输出。您会想知道为什么filer的输出为空。

with open("testFile.txt", 'w') as filer:
    sp.check_call(shlex.split("ls -lh0"), stdout=filer)

使用check_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

答案 1 :(得分:5)

这是因为调用的方式:

使用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)

只需一个列表并将其用于通话。

答案 2 :(得分:3)

这是因为参数被解释为可执行文件名。如果将"ls -lh"放入shell中也是一样。

luk32:~/projects/tests$ "ls -lh"
bash: ls -lh: command not found

有一个名为shlex.split的实用程序。

>>> import shlex, subprocess
>>> command_line = raw_input()
/bin/vikings -input eggs.txt -output "spam spam.txt" -cmd "echo '$MONEY'"
>>> args = shlex.split(command_line)
>>> print args
['/bin/vikings', '-input', 'eggs.txt', '-output', 'spam spam.txt', '-cmd', "echo '$MONEY'"]
>>> p = subprocess.Popen(args)

但我认为你不需要它。只是假设使用list是正确的方法,该工具是支持从不鼓励的shell=True模式转换。

答案 3 :(得分:2)

根据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 subprocess.call('ls -l')将失败,而在Windows中它将成功。导致问题的是os.execvp()。问题是整个字符串作为参数传递。如果执行 subprocess.call('free'),它将在UNIX中成功。