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”目录下。