完成文件
从 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 的所有可能的完成项。 |