完成文件

从 v2.05a 版本开始,bash 提供了智能的可编程完成功能。如果你已经了解 bash,那么为自己的程序/维护的项目编写这种完成功能相对容易。请参阅 bash-completion-r1.eclass,了解如何安装完成文件。

大多数这些变量在取消设置后会失去其特殊属性,即使随后被重置也是如此。

变量 用途
COMP_CWORD ${COMP_WORDS}中包含当前光标位置的单词的索引。
COMP_LINE 当前命令行。
COMP_POINT 当前光标位置相对于当前命令开头的索引。如果当前光标位置位于当前命令的末尾,则该变量的值等于 ${#COMP_LINE}
COMP_WORDBREAKS Readline 库在执行单词完成时视为单词分隔符的字符集。
COMP_WORDS 一个数组变量,包含当前命令行 ${COMP_LINE} 中的各个单词。
COMPREPLY 一个数组变量,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 的所有可能的完成项。