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

时间:2009-08-10 04:39:58

标签: python shell subprocess popen

使用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之间存在差异?

3 个答案:

答案 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,因此我不知道它的行为方式或原因。