Многократный вызов одной и той же подкоманды в одной командной строке

Я пытаюсь понять, как использовать 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 ответа

Решение

Вы спрашиваете 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']]

Ограничения: подкоманды используются как зарезервированные слова и не могут использоваться в качестве аргументов параметров.

Расщепляющий 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_parser а также check_parser не может быть других позиционных аргументов.


Обход жадных REMAINDER это использовать argparse.PARSER, Это nargs ценить это subparsers использует (недокументированное). Это как 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

Другие вопросы по тегам