Python subprocess.Popen在命令行参数中失败
在使用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()
3 个回答
这个问题是因为出现了shell 通配符匹配。
简单来说,命令中的*
在ls -al *
中会被你的命令行解释器(shell)处理,匹配所有可用的文件。当你在没有使用shell=True
这个选项的情况下运行子进程时,Python无法自己理解*
,因此就会显示错误信息ls: cannot access *: No such file or directory
。
而当你使用shell=True
运行命令时,Python实际上是把控制权交给了命令行解释器,这样就能正确显示输出了。
另外,执行包含来自不可信来源的未经处理的输入的命令会让程序面临shell注入的风险,这是一种严重的安全漏洞,可能导致任意命令的执行,因此使用时要小心(请查看这里的警告)。
编辑 1
这里的问题是shell通配符匹配和Popen
处理args
的方式造成的。
根据subprocess模块,
class subprocess.Popen
args
应该是程序参数的一个序列,或者是一个单独的字符串。如果
shell is
True,建议将
args作为一个字符串传递,而不是作为一个序列。
要理解shell通配符匹配和Popen
处理args
的方式是这里的问题,可以比较以下输出。注意在两种情况下,当shell=True
时,由于传入的是一个list
而不是string
,所以只执行了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
这是因为默认情况下,subprocess.Popen()
并不会让命令通过 shell 来解释,所以像 "*"
这样的符号不会被展开成需要的文件列表。你可以尝试在调用时加上 shell=True
作为最后一个参数。
另外,请注意文档中的警告,不要相信用户输入的内容会以这种方式被处理。