Многократный вызов одной и той же подкоманды в одной командной строке
Я пытаюсь понять, как использовать 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