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

Ben*_*oyt 20 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) ...
Run Code Online (Sandbox Code Playgroud)

因此,按照我的预期很好地打印出版本.但是在Linux上我们得到了这个:

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

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

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

为什么Windows和Linux之间存在差异?

Dav*_*ser 15

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

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

/bin/sh -c gcc --version
Run Code Online (Sandbox Code Playgroud)

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

/bin/sh -c "gcc --version" gcc --version
Run Code Online (Sandbox Code Playgroud)

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

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]
Run Code Online (Sandbox Code Playgroud)

  • 作为参考,该补丁被拒绝.查看文档是否需要修改可能是一个想法 - 我会将其留给感兴趣的一方 (3认同)

Ada*_*kin 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 shell=True上没有做任何事情,这就是为什么它以你的方式工作的原因看到.使两个系统行为相同的唯一方法是shell=True在Windows上简单地删除所有命令行参数.