Python argparse 中如何自定义标签补全

58

如何在Python脚本中让shell的Tab补全与argparse协作?

#!/usr/bin/env python
import argparse

def main(**args):
    pass

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('positional', choices=['spam', 'eggs'])
    parser.add_argument('--optional', choices=['foo1', 'foo2', 'bar'])
    args = parser.parse_args()
    main(**vars(args))

当.py文件上设置了可执行标志时,期望的结果应该是这样的:

$ ./example.py sp<tab>             
   ->  completes to "./example.py spam"
$ ./example.py --op<tab> 
   ->  completes to "./example.py --optional"
$ ./example.py --optional b<tab>
   ->  completes to "./example.py --optional bar"
$ ./example.py --optional f<tab>   
   ->  completes to "./example.py --optional foo"
       and, additionally, prints  "foo1  foo2"  choices on stdout on a new line

6
这里有一些背景信息:https://dev59.com/Vmkw5IYBdhLWcg3w6Oty(简化版)这是关于Python命令行参数解析模块argparse和过时的optparse如何响应Tab键按下的问题。 - Alex L
2个回答

87

看看 Andrey Kislyuk 的 argcomplete

使用以下命令进行安装:

pip install argcomplete

在调用 parser.parse_args() 之前,导入模块并在源代码中添加一行:

导入模块后,在调用 parser.parse_args() 之前添加以下一行:

#!/usr/bin/env python

import argparse as ap
import argcomplete

def main(**args):
  pass

if __name__ == '__main__':
  parser = ap.ArgumentParser()
  parser.add_argument('positional', choices=['spam', 'eggs'])
  parser.add_argument('--optional', choices=['foo1', 'foo2', 'bar'])
  argcomplete.autocomplete(parser)
  args = parser.parse_args()
  main(**vars(args))

为确保bash知道该脚本,您需要使用以下命令

and to make sure that bash knows about this script, you use
eval "$(register-python-argcomplete your_script)"

你应该将那一行加入到你的~/.bashrc文件中,或者按照argcomplete的文档启用“global”自动补全。

之后,自动补全会按照要求工作。

其工作原理是eval命令创建了一个名为_python_argcomlete的函数,并使用complete注册它。(运行register-python-argcomplete your_script来查看被评估成bash的内容)。自动补全函数查找由bash自动补全机制设置的环境变量以确定是否需要操作。如果需要操作,则程序立即退出。如果不需要操作,则这是对程序的正常调用,该函数不执行任何操作,程序继续正常执行。


6
如果您启用了“全局完成”功能,需要在Python文件开头放置注释# PYTHON_ARGCOMPLETE_OK(例如,作为第二行,在#!/usr/bin/env python之后),否则自动完成将无法工作。建议您编辑答案中的代码,添加上述的注释行。 - Alberto Pettarin
还有activate-global-python-argcomplete可用(我认为这可能是新的,因为我记不得上次看到它了)。 - Andy Hayden
我有一个大项目,如果这可以帮助我自动生成bash完成脚本,或以任何方式使我不必手动维护命令完成脚本呢? - shiminsh
@shiminsh 这是它的意图,但这取决于它是否适用于您。argcomplete启动应用程序以动态生成完成信息,这可能需要时间,具体取决于应用程序的结构。对我来说,这种延迟足以让我在大多数项目中不使用argcomplete。但是,如果您已经使用了argparse,那么添加argcomplete以尝试它真的很容易。 - Anthon
如果尝试使用 python your_script,这仍然有效吗? 对我来说似乎只有在我执行 chmod +x ./your_script 并尝试使用 ./your_script 运行它时才起作用。 如果这是预期的行为,我认为这个答案应该提到这一点。 - Multihunter
显示剩余6条评论

5
为了使自动完成功能起作用,您需要一个bash函数来生成可能的选项,然后运行complete -F <function_name> <program_name>
最好的方法是让程序基于自己的解析算法生成完成函数以避免重复。但是,快速查看argparse时,我没有找到访问其内部结构的方法,但建议您查找一下。
这是一个适用于上述程序的bash函数:
function _example_auto() {
    local cur=${COMP_WORDS[COMP_CWORD]}
    local prev=${COMP_WORDS[COMP_CWORD-1]}

    case "$prev" in
    --optional ) 
        COMPREPLY=( $(compgen -W "foo1 foo2 bar" -- $cur) )
        return 0
        ;;
    *)
        COMPREPLY=( $(compgen -W "--optional spam eggs" -- $cur) )
        return 0
        ;;
    esac
}

这个程序只是一个例子。对于某些程序,可以列出所有选项,但不能为库做到这一点。类似于argparseoptcomplete等效工具可以完成任务,但其他(包括部分)解决方案也可以接受。 - Denis Otkidach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接