为什么带有 shell=True 的 subprocess.Popen() 在 Linux 和 Windows 上的工作方式不同?

2022-01-18 00:00:00 python shell subprocess popen

问题描述

当使用 subprocess.Popen(args, shell=True) 运行gcc --version"(仅作为示例),在 Windows 上我们得到:

When using subprocess.Popen(args, shell=True) to run "gcc --version" (just as an example), on Windows we get this:

>>> from subprocess import Popen
>>> Popen(['gcc', '--version'], shell=True)
gcc (GCC) 3.4.5 (mingw-vista special r3) ...

所以它可以很好地打印出我所期望的版本.但在 Linux 上,我们得到了这个:

So it's nicely printing out the version as I expect. But on Linux we get this:

>>> from subprocess import Popen
>>> Popen(['gcc', '--version'], shell=True)
gcc: no input files

因为gcc没有收到--version选项.

Because gcc hasn't received the --version option.

文档没有具体说明在 Windows 下 args 应该发生什么,但它确实说,在 Unix 上,如果 args 是一个序列,第一项指定命令字符串,任何附加项将被视为额外的 shell 参数." 恕我直言,Windows 方式更好,因为它允许您将 Popen(arglist) 调用与 Popen(arglist, shell=True) 个.

The docs don't specify exactly what should happen to the args under Windows, but it does say, on Unix, "If args is a sequence, the first item specifies the command string, and any additional items will be treated as additional shell arguments." IMHO the Windows way is better, because it allows you to treat Popen(arglist) calls the same as Popen(arglist, shell=True) ones.

为什么这里有 Windows 和 Linux 的区别?


解决方案

实际上在 Windows 上,当 shell=True 时它确实使用 cmd.exe - 它预先添加 cmd.exe/c(它实际上查找 COMSPEC 环境变量,但如果不存在则默认为 cmd.exe)到 shell 参数.(在 Windows 95/98 上,它使用中间 w9xpopen 程序来实际启动命令.

Actually on Windows, it does use cmd.exe when shell=True - it prepends cmd.exe /c (it actually looks up the COMSPEC environment variable but defaults to cmd.exe if not present) to the shell arguments. (On Windows 95/98 it uses the intermediate w9xpopen program to actually launch the command).

所以奇怪的实现实际上是 UNIX 一个,它执行以下操作(每个空格分隔不同的参数):

So the strange implementation is actually the UNIX one, which does the following (where each space separates a different argument):

/bin/sh -c gcc --version

看起来正确的实现(至少在 Linux 上)应该是:

It looks like the correct implementation (at least on Linux) would be:

/bin/sh -c "gcc --version" gcc --version

因为这会从引用的参数中设置命令字符串,并成功传递其他参数.

Since this would set the command string from the quoted parameters, and pass the other parameters successfully.

来自 -csh 手册页部分:

From the sh man page section for -c:

从 command_string 操作数而不是从标准输入读取命令.特殊参数 0 将从 command_name 操作数设置,位置参数($1、$2 等)从其余参数操作数设置.

这个补丁看起来很简单:

This patch seems to fairly simply do the trick:

--- subprocess.py.orig  2009-04-19 04:43:42.000000000 +0200
+++ subprocess.py       2009-08-10 13:08:48.000000000 +0200
@@ -990,7 +990,7 @@
                 args = list(args)

             if shell:
-                args = ["/bin/sh", "-c"] + args
+                args = ["/bin/sh", "-c"] + [" ".join(args)] + args

             if executable is None:
                 executable = args[0]

相关文章