使用subprocess.Popen(args, shell=True)
运行“gcc --version
”时(仅作为示例),在Windows上我们得到了这个:
>>> from subprocess import Popen
>>> Popen(['gcc', '--version'], shell=True)
gcc (GCC) 3.4.5 (mingw-vista special r3) ...
所以很好地按照我的预期打印出版本。但是在Linux上我们得到了这个:
>>> from subprocess import Popen
>>> Popen(['gcc', '--version'], shell=True)
gcc: no input files
因为gcc没有收到--version
选项。
文档没有详细说明Windows下args应该发生什么,但它确实说,在Unix上,“如果args是一个序列,第一项指定命令字符串,任何其他项目将被视为额外的shell参数。“恕我直言,Windows方式更好,因为它允许您将Popen(arglist)
次呼叫视为Popen(arglist, shell=True)
次呼叫。
为什么Windows和Linux之间存在差异?
答案 0 :(得分:14)
实际上在Windows上,它在cmd.exe
时使用shell=True
- 它会在cmd.exe /c
之前添加(它实际上会查找COMSPEC
环境变量,但默认为cmd.exe
如果不存在)到shell参数。 (在Windows 95/98上,它使用中间w9xpopen
程序来实际启动命令。)
所以奇怪的实现实际上是UNIX
,它执行以下操作(其中每个空格分隔不同的参数):
/bin/sh -c gcc --version
看起来正确的实现(至少在Linux上)将是:
/bin/sh -c "gcc --version" gcc --version
因为这会从引用的参数中设置命令字符串,并成功传递其他参数。
来自sh
的{{1}}手册页部分:
-c
这个补丁似乎相当简单:
Read commands from the command_string operand instead of from the standard input. Special parameter 0 will be set from the command_name operand and the positional parameters ($1, $2, etc.) set from the remaining argument operands.
答案 1 :(得分:5)
来自subprocess.py源:
在UNIX上,shell = True:如果args是一个字符串,则指定 命令字符串通过shell执行。如果args是一个序列, 第一项指定命令字符串和任何其他项 将被视为额外的shell参数。
在Windows上:Popen类使用CreateProcess()来执行子级 程序,对字符串进行操作。如果args是一个序列,它将是 使用list2cmdline方法转换为字符串。请注意 并非所有MS Windows应用程序都解释命令行相同 方式:list2cmdline是为使用相同的应用程序而设计的 规则作为MS C运行时。
这不能回答原因,只是澄清你正在看到预期的行为。
“为什么”可能是在类UNIX系统上,命令参数实际上是作为字符串数组传递给应用程序(使用exec*
系列调用)。换句话说,调用进程决定进入EACH命令行参数的内容。而当你告诉它使用shell时,调用进程实际上只有机会将一个命令行参数传递给shell来执行:你想要执行的整个命令行,可执行文件名和参数,作为单个字符串。
但是在Windows上,整个命令行(根据上面的文档)作为单个字符串传递给子进程。如果查看CreateProcess API文档,您会注意到它希望将所有命令行参数连接成一个大字符串(因此调用list2cmdline
)。
另外,在类UNIX系统上,实际上 是一个可以做有用事情的shell,所以我怀疑造成差异的另一个原因是在Windows上,{{1什么都不做,这就是为什么它按照你所看到的方式工作。使两个系统行为相同的唯一方法是在Windows上shell=True
时简单地删除所有命令行参数。
答案 2 :(得分:0)
shell=True
的UNIX行为的原因与引用有关。当我们编写一个shell命令时,它将在空格处拆分,因此我们必须引用一些参数:
cp "My File" "New Location"
当我们的参数包含引号时,这会导致问题,这需要转义:
grep -r "\"hello\"" .
有时我们可以获得awful situations,其中\
也必须转义!
当然,真正的问题是我们正在尝试使用一个字符串来指定多个字符串。在调用系统命令时,大多数编程语言通过允许我们首先发送多个字符串来避免这种情况,因此:
Popen(['cp', 'My File', 'New Location'])
Popen(['grep', '-r', '"hello"'])
有时运行“原始”shell命令会很好;例如,如果我们从shell脚本或Web站点复制粘贴某些东西,并且我们不想手动转换所有可怕的转义。这就是shell=True
选项存在的原因:
Popen(['cp "My File" "New Location"'], shell=True)
Popen(['grep -r "\"hello\"" .'], shell=True)
我不熟悉Windows,因此我不知道它的行为方式或原因。