2018-12-01 15:57:04

python Tab 终端命令补全空格问题

1. 在很多情况,在编写的终端程序中会有命令交互部分


tools >show plugins<TAB>
plugins/portscan   
plugins/webinfo

tools >

希望能出现如上效果

2. python 在这方面给与了一定的处理方式


if 'linux' in sys.platform:
    import readline
elif 'darwin' in sys.platform:
    sys.path.insert(0, 'module/readline_osx')
    import readline
elif 'win32' == sys.platform or 'win64' == sys.platform:
    sys.path.insert(0, 'module/readline_windows')
    import readline
其中 readline 模块可提供TAB补全功能


url:https://docs.python.org/2/library/readline.html#example


3. 在实际使用中官方给与的例子如下

   import os
   import readline
   histfile = os.path.join(os.path.expanduser("~"), ".pyhist")
   try:
       readline.read_history_file(histfile)
       # default history len is -1 (infinite), which may grow unruly
       readline.set_history_length(1000)
   except IOError:
       pass
   import atexit
   atexit.register(readline.write_history_file, histfile)
   del os, histfile

The following example extends the :class:`code.InteractiveConsole` class to
support history save/restore. ::

   import code
   import readline
   import atexit
   import os

   class HistoryConsole(code.InteractiveConsole):
       def __init__(self, locals=None, filename="",
                    histfile=os.path.expanduser("~/.console-history")):
           code.InteractiveConsole.__init__(self, locals, filename)
           self.init_history(histfile)

       def init_history(self, histfile):
           readline.parse_and_bind("tab: complete")
           if hasattr(readline, "read_history_file"):
               try:
                   readline.read_history_file(histfile)
               except IOError:
                   pass
               atexit.register(self.save_history, histfile)

       def save_history(self, histfile):
           readline.set_history_length(1000)
           readline.write_history_file(histfile)


其中存在一个问题就是无法将带有空格的命令补全出来


4. 经过研究发现通过如下代码是可以实现

通过读取代码发现可以通过 complete_XXXXX 通过这种格式对特定命令进行自定义补全 如:complete_extra 输入 extra进行补全的时候会调用complete_extra函数,



import os
import re
import readline

COMMANDS = ['extra', 'extension', 'stuff', 'errors',
            'email', 'foobar', 'foo']
RE_SPACE = re.compile('.*\s+$', re.M)

class Completer(object):

    def _listdir(self, root):
        "List directory 'root' appending the path separator to subdirs."
        res = []
        for name in os.listdir(root):
            path = os.path.join(root, name)
            if os.path.isdir(path):
                name += os.sep
            res.append(name)
        return res

    def _complete_path(self, path=None):
        "Perform completion of filesystem path."
        if not path:
            return self._listdir('.')
        dirname, rest = os.path.split(path)
        tmp = dirname if dirname else '.'
        res = [os.path.join(dirname, p)
                for p in self._listdir(tmp) if p.startswith(rest)]
        # more than one match, or single match which does not exist (typo)
        if len(res) > 1 or not os.path.exists(path):
            return res
        # resolved to a single directory, so return list of files below it
        if os.path.isdir(path):
            return [os.path.join(path, p) for p in self._listdir(path)]
        # exact file match terminates this completion
        return [path + ' ']

    def complete_extra(self, args):
        "Completions for the 'extra' command."
        if not args:
            return self._complete_path('.')
        # treat the last arg as a path and complete it
        return self._complete_path(args[-1])

    def complete(self, text, state):
        "Generic readline completion entry point."
        buffer = readline.get_line_buffer()
        line = readline.get_line_buffer().split()
        # show all commands
        if not line:
            return [c + ' ' for c in COMMANDS][state]
        # account for last argument ending in a space
        if RE_SPACE.match(buffer):
            line.append('')
        # resolve command to the implementation function
        cmd = line[0].strip()
        if cmd in COMMANDS:
            impl = getattr(self, 'complete_%s' % cmd)
            args = line[1:]
            if args:
                return (impl(args) + [None])[state]
            return [cmd + ' '][state]
        results = [c + ' ' for c in COMMANDS if c.startswith(cmd)] + [None]
        return results[state]

comp = Completer()
# we want to treat '/' as part of a word, so override the delimiters
readline.set_completer_delims(' \t\n;')
readline.parse_and_bind("tab: complete")
readline.set_completer(comp.complete)
raw_input('Enter section name: ')



5. 通过上述方法实现了自定义TAB补全

但是可能会遇到更多其他需求比如想要获取终端想要补全的全部命令信息



    def complete(self, text, state):
        """
        :param text:
        :param state:
        :return:
        """
        buff = readline.get_line_buffer()
        line = readline.get_line_buffer().split()



可以在类里重写如上函数获取TAB需要补全的全部命令信息



>python complete.py 
Enter section name > extextension extra
Enter section name > extra foofoo.py foo.txt foo/
Enter section name > extra foo/foo/bar.txt foo/baz.txt
Enter section name > extra foo/bar.txt



参考链接:

https://stackoverflow.com/questions/5637124/tab-completion-in-pythons-raw-input

https://segmentfault.com/q/1010000006090261


发表回复