24

当使用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 的区别?

4

3 回答 3

15

实际上在 Windows 上,它确实使用了cmd.exewhen shell=True- 它在 shell 参数之前添加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]
于 2009-08-10T11:09:49.550 回答
5

从 subprocess.py 来源:

在 UNIX 上,使用 shell=True:如果 args 是字符串,它指定要通过 shell 执行的命令字符串。如果 args 是一个序列,则第一项指定命令字符串,任何其他项都将被视为额外的 shell 参数。

在 Windows 上:Popen 类使用 CreateProcess() 来执行子程序,该程序对字符串进行操作。如果 args 是一个序列,它将使用 list2cmdline 方法转换为字符串。请注意,并非所有 MS Windows 应用程序都以相同的方式解释命令行:list2cmdline 是为使用与 MS C 运行时相同规则的应用程序设计的。

这没有回答为什么,只是澄清你看到了预期的行为。

exec*“为什么”可能是在类 UNIX 系统上,命令参数实际上作为字符串数组传递给应用程序(使用调用系列)。换句话说,调用进程决定了每个命令行参数的内容。而当您告诉它使用 shell 时,调用进程实际上只有机会将单个命令行参数传递给 shell 以执行:您想要执行的整个命令行、可执行文件名称和参数,作为单个字符串。

但在 Windows 上,整个命令行(根据上述文档)作为单个字符串传递给子进程。如果您查看CreateProcess API 文档,您会注意到它希望将所有命令行参数连接在一起形成一个大字符串(因此调用list2cmdline)。

此外,在类 UNIX 系统上实际上一个 shell 可以做有用的事情,所以我怀疑造成差异的另一个原因是在 Windowsshell=True上什么都不做,这就是它以你的方式工作的原因看到。使两个系统行为相同的唯一方法是shell=True在 Windows 上简单地删除所有命令行参数。

于 2009-08-10T04:55:52.643 回答
-1

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,所以我不知道它的行为方式或原因不同。

于 2014-12-10T15:11:01.410 回答