shell 什么是subprocess.popen的args参数的最大长度?

lf3rwulv  于 2022-11-16  发布在  Shell
关注(0)|答案(2)|浏览(282)

我正在使用子进程模块中的Popen函数来执行命令行工具:

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上找到了下面的文章:Command prompt (Cmd. exe) command-line string limitation,但我不想硬编码该值。我宁愿在运行时获取该值,以便将命令分解为多个调用。
我在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的最大长度,以便知道每次运行时要发送多少条路径。*
wn9m85ua

wn9m85ua1#

如果传递shell=False,则Cmd.exe不起作用。
在Windows上,子进程将使用Win32 API中的CreateProcess函数创建新进程。此函数的documentation声明第二个参数(由subprocess.list2cmdline生成)的最大长度为32,768个字符,包括Unicode终止空字符。如果lpApplicationName为NULL,则lpCommandLine的模块名称部分限制为MAX_PATH字符。
对于您的示例,我建议为executable(args[0])提供一个值,并为第一个参数使用args。如果我对CreateProcess文档和子进程模块源代码的阅读是正确的,这应该可以解决您的问题。
[edit:在拿到Windows机器并进行测试后删除了args[1:]位]

ezykj2lf

ezykj2lf2#

对于类Unix平台,内核常量ARG_MAXdefined by POSIX.它至少需要4096字节,尽管在现代系统中,它可能是1兆字节或更多。
在许多系统上,getconf ARG_MAX将在shell提示符下显示其值。
shell实用程序xargs可以方便地分解一个长命令行。

python myscript.py *

在大目录中失败,因为文件列表扩展为一个字节长度超过ARG_MAX的值,您可以使用类似下面的方法解决此问题

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

(The选项-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()之类的 Package 函数来完成大部分工作:

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的新名称。早于Python 3.5的版本没有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,正确的实现应该会引发错误,但这只是一个快速演示。

相关问题