最后一个位置参数后不要解析选项

19 投票
4 回答
4071 浏览
提问于 2025-04-16 20:21

我正在为 ssh 命令行客户端写一个封装器。在第一个属于 command 的位置参数之后,所有后续的选项也应该被当作位置参数来处理。

optparse 中,我认为可以通过 disable_interspersed_args 来实现这个功能。

目前我有这样的代码:

parser = argparse.ArgumentParser()
parser.add_argument('--parallel', default=False, action='store_true')
# maybe allow no command? this would ssh interactively into each machine...
parser.add_argument('command', nargs='+')
args = parser.parse_args()

但是如果选项作为命令的一部分传递(比如 my_wrapper ls -l),它们会被 ArgumentParser 解释为未知选项,出现 error: unrecognized arguments: -l 的错误。

如果我使用 parse_known_args(),选项的顺序可能会被打乱。

p = argparse.ArgumentParser()
p.add_argument('-a', action='store_true')
p.add_argument('command', nargs='+')
print(p.parse_known_args())

$ python3 bah.py -b ls -l -a
(Namespace(a=True, command=['ls']), ['-b', '-l'])

在这里你可以看到,-bls 前面的位置信息丢失了,而 -a 也被从命令中解析出来,这并不是我想要的结果。

我该如何:

  • 在某个特定点之后阻止参数被解析?
  • 禁用交错参数的解析?
  • 允许带前缀的参数被当作位置参数处理?

4 个回答

0

@dcolish 提出的建议是一个通用的方法。这里有一个示例实现,它还支持标准的 -- 分隔符,但使用这个分隔符并不是正确解析的必要条件。

结果:

# ./parse-pos.py -h
usage: parse-pos.py [-h] [-qa] [-qb] COMMAND [ARGS...]

# ./parse-pos.py -qa ls -q -h aa /bb
try_argv = ['-qa', 'ls']
cmd_rest_argv = ['-q', '-h', 'aa', '/bb']
parsed_args = Namespace(command='ls', qa=True, qb=False)

代码:

#!/usr/bin/python3

import argparse
import sys
from pprint import pprint

class CustomParserError(Exception):
    pass

class CustomArgumentParser(argparse.ArgumentParser):
    def error(self, message):
        raise CustomParserError(message)
    def original_error(self, message):
        super().error(message)

def parse_argv():
    parser = CustomArgumentParser(description='Example')
    parser.add_argument('command', metavar='COMMAND [ARGS...]', help='the command to be executed')
    parser.add_argument('-qa', action='store_true') # "ambiguous option" if you specify just "-q"
    parser.add_argument('-qb', action='store_true') # "ambiguous option" if you specify just "-q"

    def parse_until_positional(parser, _sys_argv = None):
        if _sys_argv is None:
            _sys_argv = sys.argv[1:] # skip the program name
        for i in range(0, len(_sys_argv) + 1):
            try_argv = _sys_argv[0:i]
            try:
                parsed_args = parser.parse_args(try_argv)
            except CustomParserError as ex:
                if len(try_argv) == len(_sys_argv):
                    # this is our last try and we still couldn't parse anything
                    parser.original_error(str(ex)) # sys.exit()
                continue
            # if we are here, we parsed our known optional & dash-prefixed parameters and the COMMAND
            cmd_rest_argv = _sys_argv[i:]
            break
        return (parsed_args, cmd_rest_argv, try_argv)

    (parsed_args, cmd_rest_argv, try_argv) = parse_until_positional(parser)

    # debug
    pprint(try_argv)
    pprint(cmd_rest_argv)
    pprint(parsed_args)

    return (parsed_args, cmd_rest_argv)

def main():
    parse_argv()

main()
5

我觉得你解决这些问题的最好办法是,在所有可选参数后面加上 --。这个 -- 是一个伪参数,它告诉 ArgumentParser 后面的内容都是位置参数。相关文档可以在 这里 找到。

至于如何防止在某个点之后解析参数,你可以把部分 argv 传给 parse_args。结合一些自省(也就是查看对象的属性和方法)可以用来限制解析的内容。

21

我也遇到过同样的问题。我在argparse的错误追踪器上找到了解决办法:http://code.google.com/p/argparse/issues/detail?id=52

解决方法很简单:把 nargs='+'(或者 '*')替换成 nargs=argparse.REMAINDER。这个特殊的值虽然没有文档说明,但它能实现你想要的效果。

撰写回答