subprocess.Popen的args参数最大长度是多少?

22 投票
2 回答
18309 浏览
提问于 2025-04-15 20:01

我正在使用 Popen 函数,这个函数来自 subprocess 模块,用来执行命令行工具:

subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)

我使用的这个工具需要处理一系列文件。在某些情况下,这个文件列表可能会非常长。有没有办法找到这个参数 args 的最大长度?当我传递大量文件给这个工具时,我遇到了以下错误:

Traceback (most recent call last):
  File "dump_output_sopuids.py", line 68, in <module>
    uid_map = create_sopuid_to_path_dict_dcmdump(dicom_files)
  File "dump_output_sopuids.py", line 41, in create_sopuid_to_path_dict_dcmdump
    dcmdump_output = subprocess.Popen(cmd,stdout=subprocess.PIPE).communicate(0)[0]
  File "c:\python26\lib\subprocess.py", line 621, in __init__
    errread, errwrite)
  File "c:\python26\lib\subprocess.py", line 830, in _execute_child
    startupinfo)
WindowsError: [Error 206] The filename or extension is too long

有没有什么通用的方法可以找到这个最大长度?我在 msdn 上找到了一篇文章: 命令提示符 (Cmd.exe) 命令行字符串限制,但我不想把这个值写死在代码里。我更希望在运行时获取这个值,以便把命令分成多个调用。

我在 Windows XP 64 上使用 Python 2.6。

编辑:添加代码示例

paths = ['file1.dat','file2.dat',...,'fileX.dat']
cmd = ['process_file.exe','+p'] + paths
cmd_output = subprocess.Popen(cmd,stdout=subprocess.PIPE).communicate(0)[0]

这个问题出现的原因是,paths 列表中的每个实际条目通常都是非常长的文件路径,并且这些条目有几千个。

我不介意把命令分成多次调用 process_file.exe。我想找一个通用的方法来获取 args 的最大长度,这样我就知道每次运行时可以发送多少个路径。

2 个回答

5

在类Unix系统中,内核常量 ARG_MAX 是由 POSIX 定义的。它的值至少要有 4096 字节,不过在现代系统上,这个值通常会是一个兆字节或者更多。

在很多系统上,你可以通过在命令行输入 getconf ARG_MAX 来查看这个值。

命令行工具 xargs 可以帮助你把很长的命令行分开。例如,如果

python myscript.py *

在一个大目录中执行失败,因为文件列表的长度超过了 ARG_MAX 的限制,你可以用类似下面的方式来解决这个问题:

printf '%s\0' * |
xargs -0 python myscript.py

(选项 -0 是 GNU 的扩展,但这是唯一一种完全安全的方法,可以明确传递可能包含换行符、引号等的文件名列表。)你也可以看看

find . -maxdepth 1 -type f -exec python myscript.py {} +

这些方法的解决方案是,如果参数列表太长,它们会把参数分开,然后多次运行 myscript.py,每次传递尽可能多的参数。根据 myscript.py 的功能,这可能正是你想要的,或者会导致严重错误。(比如,如果它对你传入的文件中的数字求和,你会得到每组参数处理后的多个结果。)

相反,如果你想把一长串参数传递给 subprocess.Popen() 和其他相关函数,可以用类似下面的方式:

p = subprocess.Popen(['xargs', '-0', 'command'],
    stdin=subprocess.PIPE, stdout=subprocess.PIPE,
    stderr=subprocess.PIPE)
out, err = p.communicate('\0'.join(long_long_argument_list))

... 在大多数情况下,你可能应该避免直接使用 Popen(),而是让像 run()check_call() 这样的封装函数来处理大部分工作:

r = subprocess.run(['xargs', '-0', 'command'],
    input='\0'.join(long_long_argument_list),
    universal_newlines=True)
out = r.stdout

subprocess.run() 在 Python 3.7 及以上版本中支持 text=True,这是 universal_newlines=True 的新名称。3.5 之前的旧版 Python 没有 run,所以你需要使用旧的遗留函数 check_outputcheck_call 或(很少用到的)call

如果你想在 Python 中重新实现 xargs,可以参考下面的代码:

import os

def arg_max_args(args):
    """
    Split up the list in `args` into a list of lists
    where each list contains fewer than ARG_MAX bytes
    (including room for a terminating null byte for each
    entry)
    """
    arg_max = os.sysconf("SC_ARG_MAX")
    result = []
    sublist = []
    count = 0
    for arg in args:
        argl = len(arg) + 1
        if count + argl > arg_max:
            result.append(sublist)
            sublist = [arg]
            count = argl
        else:
            sublist.append(arg)
            count += argl
    if sublist:
        result.append(sublist)
    return result

像真正的 xargs 一样,你会对这个函数返回的每个子列表运行一个单独的子进程。

一个合适的实现应该在任何一个参数超过 ARG_MAX 时抛出错误,但这只是一个快速演示。

14

如果你设置了shell=False,那么Cmd.exe就不会被使用。

在Windows系统中,subprocess会使用Win32 API中的CreateProcess函数来创建新的进程。这个函数的文档说明,第二个参数(由subprocess.list2cmdline构建)最大长度为32,768个字符,包括Unicode的结束符。如果lpApplicationName为空,那么lpCommandLine中的模块名部分限制为MAX_PATH字符。

根据你的例子,我建议给可执行文件(args[0])提供一个值,并将args作为第一个参数。如果我对CreateProcess文档和subprocess模块源代码的理解是正确的,这应该能解决你的问题。

[编辑:在拿到一台Windows机器并进行测试后,删除了args[1:]的部分]

撰写回答