【问题标题】:Multiple invocation of the same subcommand in a single command line在单个命令行中多次调用同一子命令
【发布时间】:2023-09-26 04:51:01
【问题描述】:

我正在尝试弄清楚如何使用 argparser 来执行以下操作:

$ python test.py executeBuild --name foobar1 executeBuild --name foobar2 ....

getBuild 本身就是一个子命令。我的目标是让脚本能够链接一系列子命令(executeBuild 就是其中之一)并按顺序执行它们。在上面的示例中,它将执行构建,然后设置环境,然后再次执行构建。如何使用 argparse 完成此操作?我尝试了以下方法:

    main_parser = argparse.ArgumentParser(description='main commands')
    subparsers = main_parser.add_subparsers(help='SubCommands', dest='command')

    build_parser = subparsers.add_parser('executeBuild')
    build_parser.add_argument('--name', action='store', nargs=1, dest='build_name')
    check_parser = subparsers.add_parser('setupEnv')

    args, extra=main_parser.parse_known_args() 

但是,似乎每当我这样做时,它都会进入executeBuild 的子命令并报告它不知道executeBuild 是什么。我已经尝试解析额外的内容,以便我可以重复调用/链,但是,第一个视图属性似乎已被覆盖,所以我什至不能保存额外的选项并迭代。

【问题讨论】:

  • 第二个“executeBuild”出现在extra 中,因为它无法解析(即build_parser 不接受位置)。但是第二个“--name”被build_parser 识别,并像第一个一样对待。换句话说,解析器将它无法识别的东西放在“额外”中,但会继续运行。 'optionals' 可以是任意顺序并且可以重复。

标签: python argparse subcommand


【解决方案1】:

你在问argparse 它不是为它编写的:它擅长解析一个命令行(但只有一个),并且你想在一行中解析多个命令。恕我直言,您必须对参数数组进行初始拆分,然后在每个子命令上使用 argparse。下面的函数接受一个参数列表(可能是sys.argv),跳过第一个并从每个已知子命令开始拆分剩余的数组。然后您可以在每个子列表上使用 argparse :

def parse(args, subcommands):
    cmds = []
    cmd = None
    for arg in args[1:]:
        if arg in (subcommands):
            if cmd is not None:
                cmds.append(cmd)
            cmd = [arg]
        else:
            cmd.append(arg)
    cmds.append(cmd)
    return cmds

在你的例子中:

parse(['test.py', 'executeBuild', '--name', 'foobar1', 'executeBuild', '--name', 'foobar2'],
    ('executeBuild',))

=>

[['executeBuild', '--name', 'foobar1'], ['executeBuild', '--name', 'foobar2']]

限制:子命令用作保留字,不能用作选项参数。

【讨论】:

    【解决方案2】:

    事先拆分sys.argv 是一个很好的解决方案。但也可以在使用带有nargs=argparse.REMAINDER 的参数进行解析时完成。这种类型的参数获取其余的字符串,无论它们是否看起来像标志。

    用此代码替换parse_known_args

    ...
    build_parser.add_argument('rest', nargs=argparse.REMAINDER)
    check_parser.add_argument('rest', nargs=argparse.REMAINDER)
    extras = 'executeBuild --name foobar1 setupEnv executeBuild --name foobar2'.split()
    # or extras = sys.argv[1:]
    while extras:
        args = main_parser.parse_args(extras)
        extras = args.rest
        delattr(args,'rest')
        print args
        # collect args as needed
    

    打印:

    Namespace(build_name=['foobar1'], command='executeBuild')
    Namespace(command='setupEnv')
    Namespace(build_name=['foobar2'], command='executeBuild')
    

    在文档中:

    argparse.REMAINDER。所有剩余的命令行参数都被收集到一个列表中。这对于调度到其他命令行实用程序的命令行实用程序通常很有用:

    REMAINDER 的一个问题是它可能过于贪婪。 http://bugs.python.org/issue14174。因此build_parsercheck_parser 不能有其他位置参数。


    解决贪婪REMAINDER 的方法是使用argparse.PARSER。这是subparsers 使用的nargs 值(未记录)。就像REMAINDER,除了第一个字符串必须看起来像一个“参数”(没有“-”),并且匹配choices(如果给定)。 PARSER 不像 REMAINDER 那样贪婪,因此子解析器可以有其他位置参数。

    还有一些涉及“退出”字符串和虚拟解析器的额外代码。这是为了解决PARSER 参数是“必需”的事实(有点像nargs='+'

    from argparse import ArgumentParser, PARSER, SUPPRESS
    
    main_parser = ArgumentParser(prog='MAIN')
    parsers = {'exit': None}
    main_parser.add_argument('rest',nargs=PARSER, choices=parsers)
    
    build_parser = ArgumentParser(prog='BUILD')
    parsers['executeBuild'] = build_parser
    build_parser.add_argument('cmd')
    build_parser.add_argument('--name', action='store', nargs=1, dest='build_name')
    build_parser.add_argument('rest',nargs=PARSER, choices=parsers, help=SUPPRESS)
    
    check_parser = ArgumentParser(prog='CHECK')
    parsers['setupEnv'] = check_parser
    check_parser.add_argument('cmd')
    check_parser.add_argument('foo')
    check_parser.add_argument('rest',nargs=PARSER, choices=parsers, help=SUPPRESS)
    
    argv = sys.argv[1:]
    if len(argv)==0:
        argv = 'executeBuild --name foobar1 setupEnv foo executeBuild --name foobar2'.split()
    argv.append('exit') # extra string to properly exit the loop
    parser = main_parser
    while parser:
        args = parser.parse_args(argv)
        argv = args.rest
        delattr(args,'rest')
        print(parser.prog, args)
        parser = parsers.get(argv[0], None)
    

    样本输出:

    ('MAIN', Namespace())
    ('BUILD', Namespace(build_name=['foobar1'], cmd='executeBuild'))
    ('CHECK', Namespace(cmd='setupEnv', foo='foo'))
    ('BUILD', Namespace(build_name=['foobar2'], cmd='executeBuild'))
    

    另一种可能性是使用'--' 来分隔命令块:

    'executeBuild --name foobar1 -- setupEnv -- executeBuild --name foobar2'
    

    但是当有多个'--'时出现问题:http://bugs.python.org/issue13922

    【讨论】:

      最近更新 更多