关于bash的自动完成

为了庆祝新博客落成,先来发篇技术类文章。

这篇文章是我9月17日写的。因为写了以后没地方发,所以极大的推动了现在这个博客Feelyblog 2.0的编码进度。


Bash中的Tab自动补全

今天在开发一个命令行工具,由于子命令很多,我又懒得记。平常自己是Tab党嘛,那么如何让自己的命令行工具支持Tab自动补全呢。

bash中有两个内置命令,completecompgen,前者用于给bash注册补全函数,后者用于从输入片段和列表中找出匹配的补全选项。

所以要做bash补全的话,只需要写一个补全函数,用complete命令指定,然后把这个脚本扔到/etc/bash_completion.d/下面就可以了。

compgen

在说这个脚本怎么写之前,我们先来说下compgen这个命令。

1
2
3
4
5
6
7
8
compgen -W "create clean delete drop" -- "d"
# 返回如下
#delete
#drop

compgen -W "-y --h -a" -- "--"
# 返回如下
#--h

所以compgen最基本的用法就是 compgen -W "候选词列表" -- "待补全词"

有种情况特别常用,就是需要补全的内容是文件。这种用法如下:

1
compgen -f -- "待补全词"

complete

这个命令就特别简单了,用法如下

1
complete -F 补全函数 待补全命令

比如说我写了一个bash函数名称为 _foo() ,我的命令行工具叫 foo1foo,都要用这个补全函数,于是我就可以这么定义:

1
complete -F _foo foo1 foo

补全函数

前面两个命令说完了接下来就轮到重点了。如何写这个补全函数。

其实这里是有一些系统预定义好的一些变量的:

  • COMP_WORDS 数组,存放当前命令行中输入的所有单词;
  • COMP_CWORD 整数,当前光标下输入的单词位于COMP_WORDS数组中的索引
  • COMPREPLY 数组,候选的补全结果
  • COMP_WORDBREAKS 字符串,表示单词之间的分隔符
  • COMP_LINE 字符串,表示当前的命令行输入

简单的说就是我们根据上下文和compgen计算出需要的补全结果,赋值给COMPREPLY数组,然后return 0就可以了。

下面是一个例子:

假设我的命令行工具是这样的:

foo {create|clean|delete|help|version} --参数名 参数值

create 的时候可用的参数: --name 标题 -c, --config 配置文件 -y, --assumeyes 忽略询问

clean,delete 的时候可用的参数: --name 标题

help,version 直接输出并退出,没有二级参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function _a() {
local cur prev subcommand # 定义一些内部变量
COMPREPLY=() # 清空保存返回结果的变量

cur="${COMP_WORDS[COMP_CWORD]}" # 当前位置的参数
prev="${COMP_WORDS[COMP_CWORD-1]}" # 上一个参数
subcommand="${COMP_WORDS[1]}" # 第一个参数

# 可用参数表
local opts opts_create opts_clean
opts="create clean delete help version" # 二级命令
opts_create="--name -c --config -y --assumeyes" # create的参数表
opts_clean="--name" # clean和delete的参数表

#下面先判断子参数
case ${prev} in
--config|-c)
COMPREPLY=($(compgen -f -- "${cur}"))
return 0
;;
--name|-y|--assumeyes)
return 0
;;
esac

#然后补全二级命令
if [ $COMP_CWORD = 1 ]; then
COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
return 0
else
case ${subcommand} in
create)
COMPREPLY=($(compgen -W "${opts_create}" -- ${cur}))
;;
clean|delete)
COMPREPLY=($(compgen -W "${opts_clean}" -- ${cur}))
;;
help|version)
;;
esac
return 0
fi
}
complete -F _a foo

把上面这个文件保存成foo后存到/etc/bash_completion.d去就可以了。

当然一般的方法是链接过去。这样不需要你计算机里有多个这个脚本的副本。

当然如果这个工具更加复杂,也可以写出更加复杂的补全脚本来。

如果安装了git可以打开/etc/bash_completion.d/git文件,这是git的补全脚本,当然也相当复杂。