为自己的Linux程序提供二级自动补全功能
在 Linux 中, 通常可以使用Tab
键对当前已经输入的内容进行自动的补全操作, 但是这个操作对于自己的脚本一般只能抵达第一级补全, 也就是补全工具的名字, 不能补全自己脚本的第二级及后续的参数. 这个功能实际上是需要我们自己来实现的, 本文将引导您使用complete/compctl为自己的Linux程序提供二级自动补全功能.
工具介绍
- bash:
complete
是bash的一个内建命令, 通过它, 我们能为bash建立钩子以响应用户的Tab事件. - zsh:
compctl
是zsh的一个内建命令, 通过它, 我们能为zsh建立钩子以响应用户的Tab事件.
预先的准备, 环境与示范工具等
因为目标始终要针对一个工具, 我就以minecraftctl为示例, 一步一步的示范如何操作
实现示例
bash-complete
通过使用complete
命令, 可以让我们为一个命令前缀绑定上一个自动补全响应函数:
#!/bin/bash
# ~/.bashrc
_minecraftctl() {
COMPREPLY=();
# {COMP_WORDS[COMP_CWORD]} 可以获取到用户的输入
local word="{COMP_WORDS[COMP_CWORD]}";
# 这里是有关于minecraftctl的特殊实现, 稍后我将以另一个例子来解释这个操作
# 这里将会产出一个候选词列表, 以IFS为分割
local completions=`find /opt/minecraftctl/module/ -name "*.sh" -exec basename {} \; | grep -oe "^[a-zA-Z]*"`;
# 将候选词列表与用户输入的内容进行比对, 匹配到最有可能的词
COMPREPLY=(`compgen -W "{completions}" -- "{word:-h}"`)
}
# 将 minecraftctl 这个前缀绑定到 _minecraftctl 这个函数
complete -f -F _minecraftctl minecraftctl
zsh-compctl
通过使用compctl
命令, 可以让我们为一个命令前缀绑定上一个自动补全响应函数:
#!/bin/bash
# ~/.zshrc
_minecraftctl() {
# {1} 可以获取到用户对应位置的输入
local word="1";
# 这里是有关于minecraftctl的特殊实现, 稍后我将以另一个例子来解释这个操作
# 这里将会产出一个候选词列表, 以换行符为分割
local completions=`find /opt/minecraftctl/module/ -name "*.sh" -exec basename {} \; | grep -oe "^[a-zA-Z]*"`;
# 将候选词列表与用户输入的内容进行比对, 匹配到最有可能的词
reply=("${(ps:\n:)completions}");
}
# 将 minecraftctl 这个前缀绑定到 _minecraftctl 这个函数
compctl -f -K _minecraftctl minecraftctl
讲解与示例
参考上方两个脚本, 我们不难发现, bash与zsh的具体流程区别并不大, 都是为命令前缀绑定钩子函数, 获取用户输入的内容, 对已有的候选列表进行匹配后返回给用户.
在本文中, 我们来一步一步的解析这个流程.
第一步: 注册钩子
# bash
complete -f -F _minecraftctl minecraftctl
# zsh
compctl -f -K _minecraftctl minecraftctl
由于两个shell使用的工具不同, 参数不同也是很正常的事情, 这两个工具中最重要的部分就是将_minecraftctl
这个函数注册为了minecraftctl
这个命令的自动补全响应函数(这个过程也叫做注册钩子), 这样子当用户按下Tab时就会调用这个函数了
第二步: 输入与输出
此函数最大的功能就是处理输入的数据返回输出的数据, 但是由于钩子的特殊性, 输入并不一定是以参数的形式(zsh是), 输出也不是传统的return或者echo的形式, 而是以预设的变量进行返回.
获取输入
# fun _minecraftctl@bash
local word="{COMP_WORDS[COMP_CWORD]}";
# fun _minecraftctl@zsh
local word="1";
这一步中, 我们可以在函数 _minecraftctl
中获取到用户当前输入到一半的二级参数, 例如, 用户如果输入到 minecraftctl h
, 那么 ${COMP_WORDS[COMP_CWORD]}
或 $1
获取到的就是h
这里的 $1
也是可以换成 $2
之类的获取其他级的内容.
而 COMP_WORDS
这个变量则是一个特殊变量, 具体可参照文末参考文章 2
返回输出
# bash
COMPREPLY=(`compgen -W "{completions}" -- "{word:-h}"`)
#zsh
reply=("${(ps:\n:)completions}")
在这一步中, 两个shell有相当大的区别
- bash: 要求通过变量
COMPREPLY
返回一组确切的候选项(以换行符分割), 随着用户按Tab会依次选择 - zsh: 要求通过变量
reply
返回所有候选项(以空格分割), 会根据用户的输入依次显示匹配并选择
在这里附上对两个命令的简单注释, compgen
是一个比对工具, 它从 completions
里找到最有可能和 word
匹配的内容, 以换行符作为分隔符列出
而那句 ${(ps:\n:)completions}
的意思呢, 就是吧一个以换行符分割的字符串转为以空格分割的字符串(就是把\n换成空格)
第三步: 中间处理
local completions=`find /opt/minecraftctl/module/ -name "*.sh" -exec basename {} \; | grep -oe "^[a-zA-Z]*"`;
在这里我们获取一个候选词列表, 由于这个实现是minecraftctl自己的实现, 我在这里展示一下会显示的内容:
$ find /opt/minecraftctl/module/ -name "*.sh" -exec basename {} \; | grep -oe "^[a-zA-Z]*"
edit
join
help
install
listen
backup
say
stop
QQMsg
view
restart
download
start
这里是取得一个以换行符为分割的候选词列表
总结
实际上创建此钩子并不难, 主要是需要啃文档