Python subprocess.Popen在shell命令作为参数时失败

Python subprocess.Popen在shell命令作为参数时失败,python,shell,Python,Shell,正在与subprocess.Popen()斗争-为什么第一个和第三个按预期工作,而第二个找不到多个文件或目录中的任何一个?错误消息是: >ls: Zugriff auf * nicht möglich: Datei oder Verzeichnis nicht gefunden 英文译本: File not found or directory: access to * not possible 这是代码 #!/usr/bin/python # -*- coding: UTF-8 -*

正在与subprocess.Popen()斗争-为什么第一个和第三个按预期工作,而第二个找不到多个文件或目录中的任何一个?错误消息是:

>ls: Zugriff auf * nicht möglich: Datei oder Verzeichnis nicht gefunden
英文译本:

File not found or directory: access to * not possible
这是代码

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import subprocess

args = []
args.append ('ls')
args.append ('-al')

# First does work
cmd1 = subprocess.Popen(args)
cmd1.wait()

# Second does NOT work
args.append ('*')
cmd2 = subprocess.Popen(args)
cmd2.wait()


# Third does work
shellcmd = "ls -al *"
cmd3 = subprocess.Popen(shellcmd, shell=True )
cmd3.wait()

这是因为默认情况下
subprocess.Popen()
没有shell解释命令,因此
“*”
没有扩展到所需的文件列表中。尝试添加
shell=True
作为调用的最终参数


还要注意不要相信用户输入会以这种方式处理。

发生这种情况的原因是

基本上,
ls-al*
中的
*
由shell扩展,以匹配所有可用文件。 当您在没有
shell=True
标志的情况下运行子流程时,python无法单独解析
*
,因此,错误消息
ls:cannot access*:不显示此类文件或目录

当您使用
shell=True
运行命令时,python实际上会将控件传递给shell,从而显示正确的输出

另外,执行包含来自不受信任源的未初始化输入的shell命令会使程序容易受到shell注入的攻击,这是一个严重的安全缺陷,可能导致任意命令执行,因此应谨慎使用()


编辑1

shell globbing和
Popen
使用
args
的方式都是造成此问题的原因

class subprocess.Popen

args
应该是程序参数序列,或者是单个字符串

如果
shell为
True
,建议将
args
作为
string`而不是序列传递

要理解shell globbing和
Popen
使用
args
的方式是这里的问题,请比较下面的输出。注意,在2种情况下,当
shell=True
时,仅执行
ls
,因为根据建议,传递的输入是
列表
,而不是
字符串

subprocess.Popen(['ls'])                         #works
subprocess.Popen('ls')                           #works
subprocess.Popen(['ls', '-al'])                  #works
subprocess.Popen(['ls -al'])                     #doesn't work raises OSError since not a single command
subprocess.Popen('ls -al')                       #doesn't work raises OSError since not a single command
subprocess.Popen(['ls -al'], shell=True)         #works since in shell mode
subprocess.Popen('ls -al', shell=True)           #works since in shell mode & string is single command
subprocess.Popen(['ls', '-al'], shell=True)      #output corresponds to ls only, list passed instead of string, against recommendation
subprocess.Popen(['ls', '-al', '*'])             #doesn't work because of shell globbing for *
subprocess.Popen(['ls -al *'])                   #doesn't work raises OSError since not a single commandfor *
subprocess.Popen('ls -al *')                     #doesn't work raises OSError since not a single commandvalid arg
subprocess.Popen(['ls', '-al', '*'], shell=True) #output corresponds to ls only, list passed instead of string, against recommendation
subprocess.Popen(['ls -al *'], shell=True)       #works
subprocess.Popen('ls -al *', shell=True)         #works

这不是对您问题的直接回答,但您也可以尝试使用python库sh

例如:

from sh import ls

print ls("-al")

Popen上的默认值为shell=False,因此第一个和第二个都属于这种类型。它们使用args中提供的参数。如果我添加shell=True,那么它只计算args中的第一个参数,而忽略其他参数。因此,shell命令是“ls”,而不是“ls-al”或“ls-al*”。这不正确-尝试运行
subprocess.Popen(['ls'])
,您将看到与运行
subprocess.Popen(['ls','-al'])
时不同的输出。不起作用的是
subprocess.Popen(['ls','-al','*')
,这是由于与
*
相关联的shell globbing(我正在努力编辑注释,请原谅混乱)。我知道“*”在shell上的作用。我不明白的是第二个的执行过程中发生了什么,它应该将“ls-al*”输入shell。但事实并非如此。为什么不呢?@user3815773,第二个,没有外壳。这通常是人们所期望的行为——在不需要外壳的情况下拥有外壳是bug甚至安全漏洞的常见载体。当然,如果您希望扩展
*
,那么您确实需要一个shell。(…尽管可以说更好的做法是用Python生成文件列表,可能是使用glob模块,而不是依赖shell到glob)。