2.6 可编程补全

在熟悉了命令行自动补全的用法之后,如果你是一位开发人员的话,那么或许会问到这样的问题:“如何为自己所写的程序或脚本添加命令补全呢?”利用 bash 和 zsh 提供的可编程补全特性,我们可以方便地对命令补全加以定制。下面我们就从示例出发来一探究竟。

2.6.1 bash 示例

假设我编写的程序名叫 mycmd,它具有 --help--version 两个命令选项。让我们先来看看它的命令补全效果。当我输入

xiaodong@codeland:~$ mycmd -

并按 Tab 后,这时 bash 为我呈现了该命令的全部选项列表,同时补全成了 mycmd --

xiaodong@codeland:~$ mycmd -
--help     --version
xiaodong@codeland:~$ mycmd --

我接着输入 h,再按 Tab,bash 这次就自动补全了命令选项 --help。啊哈,这正是我想要的命令补全。那么,如何实现可编程补全呢?

xiaodong@codeland:~$ mycmd --h<Tab>
xiaodong@codeland:~$ mycmd --help

首先,我们需要在 /etc/bash_completion.d 目录下创建 mycmd 文件(亦即 /etc/bash_completion.d/mycmd)。这样,bash 就会自动加载我们在 mycmd 中编写的补全代码。

其次,我们在 mycmd 中编写如下用于处理命令自动补全的代码。如图 2.12 所示。

#
# Completion for mycmd
#
_mycmd() {
    local cur opts

    cur="${COMP_WORDS[COMP_CWORD]}"
    opts="--help --version"

    if [[ ${cur} == -* ]]; then
        COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
        return 0
    fi
}

complete -F _mycmd mycmd
bash 可编程补全示例

图 2.12: bash 可编程补全示例

这是一个典型的 bash 脚本。开头的 # 为注释,用于说明补全的用途。

接着我们定义了一个名为 _mycmd 的函数,该函数包含用来处理 mycmd 命令的选项的逻辑。

local 声明了两个变量:curopts。其中,cur 存储当前在命令行正输入的字,它通过 bash 内置的变量 COMP_WORDSCOMP_CWORD 获取。

  • COMP_WORDS:数组变量,包含当前命令行中单独的字。
  • COMP_CWORD:表示当前光标位置在 ${COMP_WORDS} 中的索引。

opts 则用来保存 mycmd 命令所有的命令选项。

然后,我们判断 $cur 是否为 - 打头,若为真,那么就用 compgen 命令来生成可供补全的选项列表。-W 选项后跟我们需要的 mycmd 命令选项。

与此同时,我们将 compgen 产生的输出赋给又一个 bash 内置变量 COMPREPLY。这样,当需要补全时,bash 就会采用 compgen 生成的补全列表了。

最后,我们用 complete 将补全函数 _mycmd-F 选项)与程序 mycmd 绑定在一起即可。

2.6.2 zsh 示例

现在,让我们来看看在 zsh 中又怎么实现可编程补全吧。

假如我们把 mycmd 的补全代码保存到 $HOME/.zsh/_mycmd 中的话,那么需要在 $HOME/.zshrc 里设置 $fpath,以便 zsh 能够加载我们的补全代码。

fpath=($HOME/.zsh $fpath)

下面就是我们针对 zsh 而改写的 mycmd 自动补全代码。如图 2.13 所示。

#compdef mycmd
#
# Completion for mycmd
#
_mycmd() {
    local cur opts

    cur="${words[CURRENT]}"
    opts=(--help --version)

    if [[ ${cur} == -* ]]; then
        compadd -- ${opts}
        return 0
    fi
}

_mycmd "$@"
zsh 可编程补全示例

图 2.13: zsh 可编程补全示例

第一行的注释并非普通注释(#compdef mycmd),它允许 zsh 为我们自动载入补全代码。

接下来定义的函数与变量跟 bash 示例相似,其中已经替换成 zsh 里等价的内容。

  • words 相当于 bash 中的 COMP_WORDS
  • CURRENT 与 bash 中的 COMP_CWORD 类似
  • COMPREPLY 则和 compadd 这个内置的 zsh 命令相同

要试验 mycmd 在 zsh 中的补全效果,只需先执行一下 source ~/.zshrc。从下面的例子中,你可以看到 mycmd 的命令补全跟 bash 中几乎一样,当然也带着 zsh 原本的补全功能。

xiaodong@codeland:~$ mycmd --
--help     --version

值得一提的是,zsh 本身还提供了一些辅助函数以用于补全,比如 _arguments_describe_message 等等,各位读者诸君不妨参考 zsh 的官方文档详加了解,以便用到自己的补全代码中。