在单个命令行中多次调用同一子命令

4 投票
2 回答
1598 浏览
提问于 2025-04-18 11:32

我正在尝试弄清楚如何使用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是什么。我尝试解析出多余的部分,以便可以进行重复调用/串联,但第一个视图属性似乎被覆盖了,所以我甚至无法保存额外的选项并进行迭代。

2 个回答

3

在处理命令行参数时,提前把 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 不能有其他的位置参数。


解决这个贪心问题的一种方法是使用 argparse.PARSER。这是 subparsers 使用的 nargs 值(没有文档说明)。它和 REMAINDER 类似,但第一个字符串必须看起来像一个“参数”(不能以 '-' 开头),并且会与 choices(如果有的话)进行匹配。PARSER 没有 REMAINDER 那么贪心,因此子解析器可以有其他的位置参数。

这里还有一些额外的代码涉及到一个“exit”字符串和一个虚拟解析器。这是为了绕过 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

4

你在问 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']]

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

撰写回答