为啥 subprocess.Popen 参数长度限制小于操作系统报告的长度?

Posted

技术标签:

【中文标题】为啥 subprocess.Popen 参数长度限制小于操作系统报告的长度?【英文标题】:Why is the subprocess.Popen argument length limit smaller than what the OS reports?为什么 subprocess.Popen 参数长度限制小于操作系统报告的长度? 【发布时间】:2015-06-30 08:54:40 【问题描述】:

我在 Linux 3.16.0 上运行 Python 3.4.3。我想使用subprocess.Popen 运行一个带有长单个参数(复杂的 Bash 调用)的命令,大约 200KiB。

根据getconfxargs,这应该在我的范围内:

$ getconf ARG_MAX
2097152
$ xargs --show-limits < /dev/null
Your environment variables take up 3364 bytes
POSIX upper limit on argument length (this system): 2091740
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2088376
Size of command buffer we are actually using: 131072

但是,Python 失败的限制要小得多:

>>> subprocess.Popen('echo %s > /dev/null' % ('a' * (131072-4096)), shell=True, executable='/bin/bash')
<subprocess.Popen object at 0x7f4613b58410>
>>> subprocess.Popen('echo %s > /dev/null' % ('a' * (262144-4096)), shell=True, executable='/bin/bash')
Traceback (most recent call last):
  [...]
OSError: [Errno 7] Argument list too long

请注意,Python 限制与“实际使用”命令缓冲区 xargs 报告的大致相同。这表明xargs 在某种程度上足够聪明,可以从较小的限制开始并根据需要增加限制,但 Python 不是。

问题:

    为什么 Python 限制小于 2MiB 的 OS 限制? 我可以增加 Python 限制吗? 如果是,怎么做?

【问题讨论】:

不要使用subprocess.Popen()的“单字符串”API。总是传递一个列表。避免shell=True 在这种情况下,我实际上是在子进程中依赖 Bash 功能。 试试subprocess.Popen('echo $SHELL')。我认为这会给你/bin/sh,而不是 bash。 仅仅因为 ARGMAX 显示 2097152 并不意味着,尝试131071131072 您需要进入管道或临时文件,适当刷新,避免死锁,处理流输出(communicate() 不适合我的用例)等等。无论如何,这行讨论是这个问题的话题。请用它来聊天或打开您自己的问题。 【参考方案1】:

This 其他问题与您的类似,但适用于 Windows。在这种情况下,您可以通过避免使用 shell=True 选项来绕过任何 shell 限制。

否则,您可以向subprocess.Popen() 提供文件列表,就像在该场景中所做的那样,并按照@Aaron Digulla 的建议。

【讨论】:

显然与 Windows 不同,限制并非来自 UNIX 上的 shell。【参考方案2】:

单个字符串参数的最大大小限制为131072。与python无关:

~$ /bin/echo "$(printf "%*s" 131071 "a")">/dev/null
~$ /bin/echo "$(printf "%*s" 131072 "a")">/dev/null
bash: /bin/echo: Argument list too long

实际上是MAX_ARG_STRLEN 决定了单个字符串的最大大小:

作为自 2.6.23 起的附加限制,一个参数不得长于 MAX_ARG_STRLEN (131072)。 如果您生成像“sh -c 'generated with long arguments'”这样的长调用,这可能会变得相关。 (由 Xan Lopez 和 Ralf Wildenhues 指出)

请参阅“参数数量和一个参数的最大长度”下的this discussion of ARG_MAX,以及unix.stackexchange 下的this question。

你可以在binfmts.h看到它:

/*
 * These are the maximum length and maximum number of strings passed to the
 * execve() system call.  MAX_ARG_STRLEN is essentially random but serves to
 * prevent the kernel from being unduly impacted by misaddressed pointers.
 * MAX_ARG_STRINGS is chosen to fit in a signed 32-bit integer.
 */
#define MAX_ARG_STRLEN (PAGE_SIZE * 32)
#define MAX_ARG_STRINGS 0x7FFFFFFF

~$ echo $(( $(getconf PAGE_SIZE)*32 )) 
131072

你可以传递多个长度为131071的字符串:

subprocess.check_call(['echo', "a"*131071,"b"*131071], executable='/bin/bash',stdout=open("/dev/null","w"))

但单个字符串 arg 不能超过 131071 字节。

【讨论】:

谢谢。一种解决方法是使用子进程的stdin 传递 Bash 命令。在我的测试中,我能够执行 16MiB 的单个参数,这令人困惑,因为这应该违反所有限制。但是,我没有进一步跟进,因为基于 Bash 的解决方案变得过于繁琐,我正在恢复为纯 Python。 有几种不同的方法可以使用 bash、循环、xargs 等。这取决于你在做什么。 This question 将字符串拆分为长度为 n 的子字符串帮助我解决了这个问题

以上是关于为啥 subprocess.Popen 参数长度限制小于操作系统报告的长度?的主要内容,如果未能解决你的问题,请参考以下文章

为啥不在 Python 中的 subprocess.Popen 中使用 `shell=True`? [复制]

从 subprocess.Popen 将参数传递给 argparse

Windows 上的 Python 2.6:如何使用“shell=True”参数终止 subprocess.Popen?

subprocess的popen函数

subprocess.Popen 和缓冲的进程输出

subprocess.Popen 需要帮助