Python readline,使用Cmd接口进行TAB补全循环

6 投票
2 回答
1385 浏览
提问于 2025-04-18 16:34

我在用Python的cmd.Cmd类来给我的程序提供一个简单的命令行输入界面。

这是一个完整的例子:

from cmd import Cmd

class CommandParser(Cmd):

    def do_x(self, line):
        pass

    def do_xy(self, line):
        pass

    def do_xyz(self, line):
        pass

if __name__ == "__main__":
    parser = CommandParser()
    parser.cmdloop()

按两次Tab键会显示可选项。再按一次Tab键也是一样的效果。

我想问的是,怎么让选项在第三次按Tab键时循环显示?在命令行的术语中,我觉得这叫做Tab: menu-complete,但我不知道怎么把这个应用到Cmd实例上。

我已经尝试过:

readline.parse_and_bind('Tab: menu-complete')

在创建解析器实例之前和之后都试过,但都没有成功。

我还试过把"Tab: menu-complete"传给Cmd的构造函数,但也没成功。

有没有人知道该怎么做?

谢谢!

2 个回答

1

很遗憾,看来解决这个问题的唯一办法就是对 cmdloop 方法进行“猴子补丁”,也就是修改 cmd.Cmd 类的方法,或者自己写一个。

正确的做法是使用 "Tab: menu-complete",但这个设置在类中被覆盖了,具体在第115行:readline.parse_and_bind(self.completekey+": complete"),所以它从来没有被激活过。(关于第115行和整个 cmd 包的内容,可以查看这个链接:https://hg.python.org/cpython/file/2.7/Lib/cmd.py)。我在下面展示了这个函数的修改版本,以及如何使用它:

import cmd


# note: taken from Python's library: https://hg.python.org/cpython/file/2.7/Lib/cmd.py
def cmdloop(self, intro=None):
    """Repeatedly issue a prompt, accept input, parse an initial prefix
    off the received input, and dispatch to action methods, passing them
    the remainder of the line as argument.
    """

    self.preloop()
    if self.use_rawinput and self.completekey:
        try:
            import readline
            self.old_completer = readline.get_completer()
            readline.set_completer(self.complete)
            readline.parse_and_bind(self.completekey+": menu-complete")  # <---
        except ImportError:
            pass
    try:
        if intro is not None:
            self.intro = intro
        if self.intro:
            self.stdout.write(str(self.intro)+"\n")
        stop = None
        while not stop:
            if self.cmdqueue:
                line = self.cmdqueue.pop(0)
            else:
                if self.use_rawinput:
                    try:
                        line = raw_input(self.prompt)
                    except EOFError:
                        line = 'EOF'
                else:
                    self.stdout.write(self.prompt)
                    self.stdout.flush()
                    line = self.stdin.readline()
                    if not len(line):
                        line = 'EOF'
                    else:
                        line = line.rstrip('\r\n')
            line = self.precmd(line)
            stop = self.onecmd(line)
            stop = self.postcmd(stop, line)
        self.postloop()
    finally:
        if self.use_rawinput and self.completekey:
            try:
                import readline
                readline.set_completer(self.old_completer)
            except ImportError:
                pass

# monkey-patch - make sure this is done before any sort of inheritance is used!
cmd.Cmd.cmdloop = cmdloop

# inheritance of the class with the active monkey-patched `cmdloop`
class MyCmd(cmd.Cmd):
    pass

一旦你对类的方法进行了“猴子补丁”(或者实现了自己的类),它就会提供正确的功能(虽然没有高亮和反向制表,但这些可以根据需要用其他按键来实现)。

2
readline.parse_and_bind("tab: menu-complete : complete")

最简单的办法是在 menu-complete 后面加一个空格:

parser = CommandParser(completekey="tab: menu-complete ")

然后执行的绑定表达式会变成

readline.parse_and_bind(self.completekey+": complete")

在第二个空格之后的内容其实会被忽略,所以这和 tab: menu-complete 是一样的。

如果你不想依赖这种 readline 解析的行为(我没见过相关文档),你可以使用一个不允许被扩展为 completekey 的 str 子类:

class stubborn_str(str):
    def __add__(self, other):
        return self

parser = CommandParser(completekey=stubborn_str("tab: menu-complete"))

self.completekey+": complete" 现在和 self.completekey 是一样的。

撰写回答