subprocess.Popen的args参数最大长度是多少?
我正在使用 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 个回答
在类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_output
、check_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
时抛出错误,但这只是一个快速演示。
如果你设置了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:]的部分]