1. 引言
当我们输入ls
再按下TAB时, 会自动列出当前路径下所有的文件;
当我们输入ls a
再按下TAB时, 会自动列出当前路径下所有以a开头的文件; 若只有一个以a开头的文件, 将会自动补全;
当我们输入type
再按下TAB时, 会自动列出全所有可执行的命令;
当我们输入docker rmi
再按下TAB时, 会自动列出所有镜像名;
一个显示文件, 一个显示命令, 一个显示容器名, 这是怎么做到的?
本文将带你一探究竟, 并以docker为例, 实现一个简单的docker自动补全规则
2. complete命令
上述功能, 是 Bash 2.05 版本新增的功能, 叫做自动补全. 自动补全允许我们对命令和选项设置补全规则, 按下TAB之后, 会根据我们设置的规则返回补全列表, 当补全列表只有一个元素时, 就会自动补全.
bash自动补全用到最主要的命令就是complete
, 这是一个Bash的内置命令(builtin), 用于指定某个命令的补全规则, complete语法如下:
complete [-abcdefgjksuv] [-o comp-option] [-DEI] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [name …]
complete -pr [-DEI] [name …]
选项:
-o comp-option
定义一些补全的行为, 可以使用的行为如下:
nospace 补全后不在最后添加空格
nosort 对于补全列表不要按字母排序
-A action
使用预设的补全规则, 可使用的补全规则如下:
alias 补全列表设置为所有已定义的别名. 等同于-a
builtin 补全列表设置为所有shell内置命令. 等同于-b
command 补全列表设置为所有可执行命令. 等同于-c
directory 补全列表设置为当前路径下所有目录. 等同于-d,
也就是说 complete -d xxx 与 complete -A directory xxx 等价, 只是写法不一样
export 补全列表设置为所有环境变量名. 等同于-e
file 补全列表设置为当前路径下所有文件. 等同于-f
function 补全列表设置为所有函数名
signal 补全列表设置为所有信号名
user 补全列表设置为所有用户名. 等同于-u
variable 补全列表设置为所有变量名. 等同于-v
-F function
用函数来定义补全规则, 函数运行后 COMPREPLY 变量做为补全列表
-W wordlist
用一个字符串来做为补全列表
-p name
显示某个命令的补全规则, 如果 name 为空的话则显示所有命令的补全规则
-r
移除某个命令的补全规则
ls
命令默认的补全列表是当前路径下所有文件, 现在, 我们改变其补全规则, 让其补全列表变为所有可执行命令
$ cd /
# 先测试下 ls 默认的补全规则
$ ls<TAB>
bin/ boot/ dev/ etc/ home/ lib/ lib32/ lib64/ libx32/ media/ mnt/ opt/ proc/ root/ run/ sbin/ srv/ sys/ tmp/ usr/ var/
# 修改 ls 的补全规则, 让所有可执行命令作为其补全列表
$ complete -c ls
# 测试修改补全规则后的 ls
$ ls who<TAB>
who whoami whoopsie whoopsie-preferences
提示: 上述改变的补全规则只在当前shell有效, 即不会影响到其他用户, 重新登录后也会失效. 所以想要恢复ls
命令的补全规则的话, 只需要退出再重新登录服务器就好了. 至于如何永久改变补全规则, 请看后文.
我们再来看下type
命令预设的补全规则, 发现type
命令设置的补全列表是所有可执行命令
$ complete -p type
complete -c type
至此, 我们应该知道引言中所提出的问题, 为什么ls
命令会文件而type
命令会列出命令
3. 自定义补全列表
尽管Bash预设了很多补全规则, 但是很明显, 如果我们自己想给docker
命令写补全规则的话, 预设的补全规则显然是不能满足我们需求的. 所以, 我们可以用-W
选项来自定义补全列表.
假设我们自己写了个mydocker
命令, 可以使用的功能有mydocker rm
, mydocker rmi
, mydocker stop
, mydocker start
, 显然, mydocker
的补全列表为rm rmi stop start
, 我们可以使用下面的命令来设置补全规则
# 将 rm rmi stop start 设置为 mydocker 的补全列表
$ complete -W 'rm rmi stop start' mydocker
$ mydocker <TAB>
rm rmi start stop
$ mydocker st<TAB>
start stop
注意: mydocker命令本身没有自动补全, 需要手动完整输入
到这一步, 我们已经能给相当一部分的命令来定义补全规则了. 但是, 上述的'-W'选项, 是静态的补全规则, 不会随着某些条件的改变而变化; docker rmi <TAB>
所有显示的镜像名, 会随着镜像的增删而改变; docker rm <TAB>
所有显示的容器名, 会随着容器的增删而改变; 是动态的补全规则, 这是如何做到的呢?
我们直接通过-p
选项来查看docker预设的补全规则就好了, 发现docker命令是通过-F _docker
来指定补全规则; 再通过type _docker
来查看_docker
是什么玩意, 发现_docker
是一个非常复杂的函数
$ complete -p docker
complete -F _docker docker
$ type _docker
_docker is a function
_docker ()
{
......
}
接下来, 我们来好好聊一聊-F
这个选项
4. 动态补全列表
-F
选项会指定一个函数做为补全规则, 每次按下TAB时, 就会调用这个函数, 并且将COMPREPLY
的值做为补全列表, 所以我们需要在函数中处理COMPREPLY
变量
除了COMPREPLY
变量外, Bash还提供了一些变量来方便我们获取当前的输入
变量名 | 类型 | 说明 |
---|---|---|
COMP_LINE | 字符串 | 当前的命令行输入的所有内容 |
COMP_WORDS | 数组 | 当前的命令行输入的所有内容, 和COMP_LINE 不同的是, 这个变量是一个数组 |
COMP_CWORD | 整数 | 当前的命令行输入的内容位于COMP_WORDS 数组中的索引 |
COMPREPLY | 数组 | 补全列表 |
接下来我们编写一个补全脚本来测试这些变量, 脚本名字可以随便取, 暂且叫做 test.sh, 文件内容如下:
_complete_test() {
echo
echo "COMP_LINE: $COMP_LINE" # 当前的命令行输入的所有内容(字符串)
echo "COMP_WORDS: ${COMP_WO