为什么subprocess.Popen()在Linux上壳=真正的工作方式不同VS的Windows?

2019-06-18 06:02发布

当使用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上,“如果参数表是一个顺序,第一项规定的命令字符串,以及任何其他项目都将被视为额外的壳参数“。 恕我直言,Windows的方式比较好,因为它可以让你把Popen(arglist)调用一样Popen(arglist, shell=True)的。

为什么选择Windows和Linux在这里有什么区别?

Answer 1:

其实在Windows上,它使用cmd.exeshell=True -它预先考虑cmd.exe /c (它实际上查找该COMSPEC环境变量,但默认为cmd.exe ,如果不存在)的壳论点。 (在Windows 95/98它采用了中间w9xpopen程序实际启动命令)。

因此,奇怪的实现实际上是UNIX一个,后者执行以下步骤(其中每个空间分隔不同的说法):

/bin/sh -c gcc --version

它看起来像正确的实现(至少在Linux上)将是:

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

由于这将从引用参数设置的命令字符串,并顺利通过其它参数。

sh的手册页节-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.

这个补丁似乎相当简单的伎俩:

--- 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]


Answer 2:

从subprocess.py来源:

在UNIX中,与壳=真:如果ARGS是一个字符串,它指定了命令串通过壳来执行。 如果ARGS是一个序列中,第一项指定的命令字符串,和任何另外的项目将被视为附加的壳参数。

在Windows上:在POPEN类使用的CreateProcess()来执行的子计划,这对字符串进行操作。 如果ARGS是一个序列,它会被转换为使用list2cmdline方法的字符串。 请注意,并非所有的MS Windows应用程序解释命令行相同的方式:list2cmdline是专为使用相同的规则MS C运行的应用程序。

那并没有回答为什么,只是明确了你所看到的预期行为。

“为什么”是可能是类UNIX系统,命令参数实际上是通过传递给应用程序(使用exec*家庭电话)作为一个字符串数组。 换句话说,调用进程决定什么进入每个命令行参数。 当你告诉它使用一个外壳,而调用进程实际上只到达一个命令行参数传递给shell执行的机会:您要执行的,可执行文件名和参数的整个命令行,作为一个字符串。

但在Windows,整个命令行(根据上述文档)被作为单个字符串子进程通过。 如果你看看CreateProcess的 API文档,你会发现,它希望所有的命令行参数一起连接成一个大的字符串(因此调用list2cmdline )。

再加上有一个事实,即在类UNIX系统存在实际上一个外壳,可以做的有益的事情,所以我怀疑是其他原因不同的是,在Windows上, shell=True什么也不做,这就是为什么它是工作的这样,你所看到的。 只有这样,才能使这两个系统相同的行为将它简单地放弃所有的命令行参数时, shell=True在Windows上。



Answer 3:

究其原因,在UNIX行为shell=True与报价的事情。 当我们写一个shell命令,它会在空间进行分割,所以我们要引用一些参数:

cp "My File" "New Location"

这导致当我们的论点包含引号,这需要逃避问题:

grep -r "\"hello\"" .

有时候,我们可以得到可怕的情况 ,其中\必须得逃!

当然,真正的问题是,我们试图用一个字符串来指定多个字符串。 当调用系统命令,大多数编程语言避免这种情况使我们能够在第一时间发送多个字符串,因此:

Popen(['cp', 'My File', 'New Location'])
Popen(['grep', '-r', '"hello"'])

有时候,它可以很好的运行“原始” shell命令; 例如,如果我们从一个shell脚本或网站的复制粘贴的东西,而我们不想把所有可怕的手动逃逸。 这就是为什么shell=True选项存在:

Popen(['cp "My File" "New Location"'], shell=True)
Popen(['grep -r "\"hello\"" .'], shell=True)

我不熟悉Windows,所以我不知道如何或为何表现不同。



文章来源: Why does subprocess.Popen() with shell=True work differently on Linux vs Windows?