8.8 一个可编程补全的例子(A Programmable Completion Example)
通常要获得比 complete 和 compgen 命令提供的默认动作更多的补全功能,是使用 shell 函数并使用“complete -F”将其绑定到特定命令上。
下面的函数所提供的补全是用于内建命令 cd 的。这是一个非常好的例子来说明当使用补全时 shell 函数要做什么。该函数使用作为“$2”传入的单词来决定目录名补全。你可以使用数组变量 COMP_WORDS,并且使用变量 COMP_CWORD 索引当前单词。
该函数依赖内建命令 complete 和 compgen 来做大部分的工作。在接受基本目录名之外,只需要添加一小部分关于 cd 命令的工作:波浪号扩展(参见 3.5.2《波浪号扩展》),在变量“$CDPATH”中搜索目录(参见 4.1《Bourne Shell 内建命令》),和对 shell 选项“cdable_vars”(参见 4.3.2《内建命令 shopt》)的基本支持。函数“_comp_cd”修改了变量 IFS 的值,以便其只包含一个换行来满足包含空格和制表符的文件名。compgen 用来打印其产生的可行补全,每个一行。
可行补全会放入数组变量 COMPREPLY 中,每个补全为一个数组元素。当函数返回时可编程补全系统会从该数组取回所有补全。
# A completion function for the cd builtin
# based on the cd completion function from the bash_completion package
_comp_cd()
{
local IFS=$' \t\n' # normalize IFS
local cur _skipdot _cdpath
local i j k
# Tilde expansion, which also expands tilde to full pathname
case "$2" in
\~*) eval cur="$2" ;;
*) cur=$2 ;;
esac
# no cdpath or absolute pathname -- straight directory completion
if [[ -z "${CDPATH:-}" ]] || [[ "$cur" == @(./*|../*|/*) ]]; then
# compgen prints paths one per line; could also use while loop
IFS=$'\n'
COMPREPLY=( $(compgen -d -- "$cur") )
IFS=$' \t\n'
# CDPATH+directories in the current directory if not in CDPATH
else
IFS=$'\n'
_skipdot=false
# preprocess CDPATH to convert null directory names to .
_cdpath=${CDPATH/#:/.:}
_cdpath=${_cdpath//::/:.:}
_cdpath=${_cdpath/%:/:.}
for i in ${_cdpath//:/$'\n'}; do
if [[ $i -ef . ]]; then _skipdot=true; fi
k="${#COMPREPLY[@]}"
for j in $( compgen -d -- "$i/$cur" ); do
COMPREPLY[k++]=${j#$i/} # cut off directory
done
done
$_skipdot || COMPREPLY+=( $(compgen -d -- "$cur") )
IFS=$' \t\n'
if
# variable names if appropriate shell option set and no completions
if shopt -q cdable_vars && [[ ${#COMPREPLY[@]} -eq 0 ]]; then
COMPREPLY=( $(compgen -v -- "$cur") )
fi
return 0
}
我们可以使用 complete 的“-F”选项来安装这个补全函数:
# Tell readline to quote appropriate and append slashes to directories;
# use the bash default completion for other arguments
complete -o filenames -o nospace -o bashdefault -F _comp_cd cd
自从我们让 Bash 和 Readline 来为我们处理一些其它的细节,我们会使用一些其它选项来告诉 Bash 和 Readline 应该做什么。“-o filenames”选项告诉 Readline 可行补全应该被视为文件名,并且应该被恰当地引起来。该选项也会使 Readline 向确定为目录的文件名后追加一个斜线(这就是为什么如果使用的目录是通过 CDPATH 找到的,我们要追加一个斜线来扩展“_comp_cd”。因为 Readline 不能告知这些补全是目录。)。“-o nospece”选项会告诉 Readline 不要向目录名后追加空格,万一我们要手动向其追加。“-o bashdefault”选项将引入剩余的 Bash 默认补全 —— Bash 添加到默认 Readline 集中的可行补全。这些包括命令名补全,由“$”或“${”开头单词的变量补全,包含路径名扩展样式(参见 3.5.8《文件名扩展》)的补全等等。
一旦使用 complete 命令安装了“_comp_cd”函数,该函数会在 cd 命令每一次试图进行单词补全时调用。
有许多的用于大多数公共 GNU、Unix 和 Linux 命令的大量的补全集合实例都是作为“bash_completion”项目的一部分。其会默认安装到许多 GNU/Linux 发布版本上。最初由伊恩•麦克唐纳德(Ian Macdonald)编写。该项目现在存在于https://github.com/scop/bash-completion/。这也有用于其它系统的,例如 Solaris 和 MacOS X 的。
老版本的“bash_completion”包已与 Bash 一起发布,其存入于“examples/complete”目录下。