完成文件
从 v2.05a 版本开始,bash
提供了智能的可编程完成功能。如果你已经了解 bash,那么为自己的程序/维护的项目编写这种完成功能相对容易。请参阅 bash-completion-r1.eclass,了解如何安装完成文件。
与完成相关的内部 Bash 变量
大多数这些变量在取消设置后会失去其特殊属性,即使随后被重置也是如此。
变量 | 用途 |
---|---|
COMP_CWORD
|
在${COMP_WORDS} 中包含当前光标位置的单词的索引。 |
COMP_LINE
|
当前命令行。 |
COMP_POINT
|
当前光标位置相对于当前命令开头的索引。如果当前光标位置位于当前命令的末尾,则该变量的值等于 ${#COMP_LINE} 。 |
COMP_WORDBREAKS
|
Readline 库在执行单词完成时视为单词分隔符的字符集。 |
COMP_WORDS
|
一个数组变量,包含当前命令行 ${COMP_LINE} 中的各个单词。 |
COMPREPLY
|
一个数组变量,bash 从中读取由完成函数生成的可能的完成项。 |
与完成相关的 Bash 内置命令
请参阅 man bash
,了解这些内置命令及其选项的完整描述。
内置命令 | 用法 |
---|---|
compgen
|
compgen [-abcdefgjksuv] [-o option] [-A action] [-G globpat] [-W wordlist] [-P prefix] [-S suffix] [-X filterpat] [-F function] [-C command] [word] 根据选项显示可能的完成项。旨在从生成可能的完成项的 shell 函数中使用。如果提供了可选的 WORD 参数,则会生成与 WORD 的匹配项。 |
complete
|
complete [-abcdefgjksuv] [-pr] [-o option] [-A action] [-G globpat] [-W wordlist] [-P prefix] [-S suffix] [-X filterpat] [-F function] [-C command] [name ...] 对于每个 NAME,指定如何完成参数。如果提供了 -p 选项,或者没有提供任何选项,则会以允许将其作为输入重复使用的方式打印现有的完成规范。-r 选项将删除每个 NAME 的完成规范,或者如果未提供任何 NAME,则删除所有完成规范。 |
对于非常简单的用例,一个简单的 complete
语句就足够了。例如,最小的 cd
完成(最小指的是不支持 ${CDPATH}
)将像这样简单:
complete -o nospace -d cd
完成函数的结构
几乎所有完成函数都将以相同的方式开始。对于这些情况,以下内容可用作创建新完成函数的模板
01: _foo() {
02: local cur prev opts
03: COMPREPLY=()
04: cur="${COMP_WORDS[COMP_CWORD]}"
05: prev="${COMP_WORDS[COMP_CWORD-1]}"
06: opts=""
07:
08: if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
09: COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
10: return 0
11: fi
12:
13: case "${prev}" in
14: # ...
15: esac
16: }
17: complete -F _foo foo
行 | 解释 |
---|---|
1 | 完成函数名称的约定通常是 _NAME,其中 NAME 是你要为其编写完成函数的应用程序/函数的名称。如果 NAME 包含一个 '-', 则应该将其替换为 '_'. 因此,bash-completion-config 将变为 _bash_completion_config()。如果不这样做,如果 bash 在 POSIX 模式下被调用,而一个包含 '-' 的函数名称在环境中(POSIX sh 不允许函数名称中包含 '-'),则可能会导致奇怪的错误。 |
3 | 重置 ${COMPREPLY} 数组,因为它可能已从先前调用中设置。 |
4 | 将局部变量 ${cur} 设置为命令行的当前单词。如果当前命令行 ${COMP_LINE} 是 'foo --fil',那么 ${cur} 将等于 '--fil'。如果 ${COMP_LINE} 等于 'foo --file '(注意末尾的空格),则 ${cur} 为空。 |
5 | 将局部变量 ${prev} 设置为命令行的前一个单词。 ${prev} 是 ${cur} 之前的单词。 |
6 | 设置局部变量 ${opts} 。在一个真正的完成函数中,这个变量将被设置为 foo 识别的所有选项。 |
8 | 测试当前单词是否等效于 -*(一个选项)或者是否正在对第一个单词进行补全(即 ${COMP_CWORD} == 1)。 |
9 | 如果测试结果为真,则显示可用的选项 ${opts} 。 compgen 的 -W 选项告诉 bash 对单词列表(字符串或计算结果为字符串的东西)进行补全。在大多数情况下,你将传递 -- ${cur} 给 compgen ,告诉它只返回与 ${cur} 匹配的完成项。 |
13 | 大多数时候,你希望在 ${prev} 等于某个选项时执行某个操作。例如,如果 foo 有一个 --file 选项(以及 -f 的简写),它可以接受任何类型的文件,那么你可以这样做case "${prev}" in
-f|--file)
COMPREPLY=( $(compgen -f ? ${cur}) )
;;
esac
|
17 | 告诉 bash 使用 _foo 函数来生成 foo 应用程序/函数的所有可能的完成项。 |
真实世界中的例子
对于本文档,我将带你通过一个真实的例子,并为 revdep-rebuild
编写一个真实的完成函数(在你阅读本文时,它甚至可能在 gentoo-bashcomp
中可用:])。
01: _revdep_rebuild() {
02: local cur prev opts
03: COMPREPLY=()
04: cur="${COMP_WORDS[COMP_CWORD]}"
05: prev="${COMP_WORDS[COMP_CWORD-1]}"
06: opts="-X --package-names --soname --soname-regexp -q --quiet"
07:
08: if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] || \
09: [[ ${prev} == @(-q|--quiet) ]] ; then
10: COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
11: return 0
12: fi
13:
14: case "${prev}" in
15: -X|--package-names)
16: _pkgname -I ${cur}
17: ;;
18: --soname)
19: local sonames=$(for x in /lib/*.so?(.)* /usr/lib*/*.so\?(.)* ; do \
20: echo ${x##*/} ; \
21: done)
22: COMPREPLY=( $(compgen -W "${sonames}" -- ${cur}) )
23: ;;
24: --soname-regexp)
25: COMPREPLY=()
26: ;;
27: *)
28: if [[ ${COMP_LINE} == *" "@(-X|--package-names)* ]] ; then
29: _pkgname -I ${cur}
30: COMPREPLY=(${COMPREPLY[@]} $(compgen -W "${opts}"))
31: else
32: COMPREPLY=($(compgen -W "${opts} -- ${cur}"))
33: fi
34: ;;
35: esac
36: }
37: complete -F _revdep_rebuild revdep-rebuild
第 1-12 行与上一节中的内容几乎相同。
行 | 解释 |
---|---|
15 | 如果 ${prev} 等于 -X 或 --package-names ,则调用 _pkgname (由 gentoo-bashcomp 定义的一个函数,它对包名进行补全 - 它设置 ${COMPREPLY} ,因此我们在这里无需担心)。 |
18 | 如果 ${prev} 等于 --soname ,则生成 /lib 和 /usr/lib* 中所有共享库的列表。将该列表传递给 compgen 以生成与 ${cur} 匹配的可能的完成项列表。 |
24 | 显然,我们无法对任何正则表达式进行补全,因此如果 ${prev} 等于 --soname-regexp ,则什么也不做。 |
27 | 对于任何其他内容(上面 case 语句中未指定的任何选项或 case 语句中指定的任何选项的任何参数),执行测试。由于 --package-names 可以接受多个包名,因此我们希望继续对包名进行补全,直到遇到另一个识别的选项(即 ${prev} )。 |
30 | 由于 _pkgname 设置了 ${COMPREPLY} ,而我们希望添加到该列表中,因此我们必须使用 COMPREPLY=(${COMPREPLY[@] ... ) 结构。 |
37 | 告诉 bash 使用 _revdep_rebuild 来生成 revdep-rebuild 的所有可能的完成项。 |