总结下bash的补全
终端下按两次tab就是补全
补全常见的分为下面几种场景:
- 第一部分的命令补全
- 命令补全后补全路径到文件
- 变量补全
- 类似
systemctl
命令的选项补全
当然bash支持自定义补全,也就是让我们实现和systemctl
命令那样补全
假设我们要systemctl disable --now firewalld
systemctl空格
后按tab键
会显示systemctl
的第二个部分的所有支持的选项
此时继续输入di
按tab键
会补全成systemctl disable
,输入--now
(相似的太多了,所以基本输入完)补全,当然最后的服务名此时补全不了,因为systemctl的补全不怎么完美
通过上面的过程知道了补全不单单是常见的前两种场景,如果我们使用第四种场景去自定义会大大加快我们的工作效率
一般我们会安装bash-completion包来得到更好的补全效果,这个包提供了一些现成的命令补全脚本,一些基础的函数方便编写补全脚本,还有一个基本的配置脚本。但也正如之前说的,这个包不是必须的,只不过可以省些力气。
bash补全相关脚本和加载流程
官方文档就那么点介绍,网上看到的博客也不详细,看了一些官方的补全脚本注释又太少,这里写下我的摸索经验
安装了bash-completion
后会有下面这几个文件(建议最好有两台环境,一个安装一个不安装方便文章后面去测试)
PS: 很多安装的软件都有补全脚本,可以去/usr/share/bash-completion/completions/
看看,有的话cp到/etc/bash_completion.d/
里即可生效
1 | /etc/profile.d/bash_completion.sh |
先看下/etc/profile.d/bash_completion.sh
,毕竟在/etc/profile.d
目录里,/etc/profile
里有source /etc/profile.d/*.sh
类似代码
所以/etc/profile.d/bash_completion.sh
会被先执行,代码内容为
1 | # Check for interactive bash and that we haven't already been sourced. |
先判断是否是交互式shell,不是就退出
然后判断shell的版本号
1 | [root@guan ~]# echo $BASH_VERSION |
bmajor
是4,bminor
是2,判断版本号是4以上才执行逻辑shopt -q progcomp
是判断是否打开了可编程的补全功能,默认是开启的,而且存在可读的/usr/share/bash-completion/bash_completion
文件,则source
它
上面文件里代码接近2000行,只讲解大致流程
设置自定义补全脚本读取的目录
- 激活扩展的模式匹配和可编程的补全功能
- 设置一些命令只补全操作对象的关键字,比如
groups
命令只补全组名、unalias
只补全当前别名、unset
只补全已有变量…… - BASH_COMPLETION_COMPAT_DIR 定义了加载bash补全脚本的目录,默认是
/etc/bash_completion.d
- 补全的时候转义一些符号,例如
mkdir "a'b"
创建了一个名为a'b
的目录,你rm的时候希望补全的是a\'b
,而不是a'b
此处挖坑,日后有空看再在这继续写/usr/share/bash-completion/bash_completion里函数的使用
quote
转换传入$1的单引号为'\''
,并在首位加上单引号,确保被shell展开后还是传进来的结果,用于被shell展开后是理想的结果
1 | $ str=`quote "test'a"` |
开始写补全脚本
bash补全最常见的是三个内置命令
- compgen
- complete
- compopt
点击查看中文文档
看起来选项和选项的值还挺多的,但是做几个例子就能记住常见的选项和值以及各种内置环境变量了
下列是常见的选项
1 | -F function 执行shell 函数,函数中生成COMPREPLY作为候选的补全结果 |
三个命令中前两个命令选项都一样,compgen
用来产生补全匹配的候选单词,complete
使用函数来生成补全的候选单词,compopt
只能用于compelete
生成补全单词的函数内部
内置变量
除了上面三个命令外,Bash还有几个内置变量来辅助补全功能,如下:
COMP_WORDS 类型为数组,存放当前命令行中输入的所有单词.被COMP_WORDBREAKS拆分成单词
COMP_CWORD 类型为整数,当前输入的单词在COMP_WORDS中的索引
COMPREPLY 类型为数组,候选的补全结果就是从这个数组
COMP_WORDBREAKS 类型为字符串,表示单词之间的分隔符
COMP_LINE 类型为字符串,表示当前的命令行输入字符
COMP_POINT 类型为整数,表示光标在当前命令行的哪个位置
注意这里的候选和补全,候选是你按tab按键的输出,但是它不一定是帮你补全,你得让补全去补全候选里的单词
命令和变量的使用
建议下面步骤在没有安装bash-compelete环境下练习,因为部分命令官方也写了补全,可能会和我下面例子冲突
- 使用
compgen
生成匹配的单词候选补全
1 | $ compgen -W 'word1 word2 word3 test' t |
从上面的compgen看出来是打印在标准输出的,t和word为你按了tab后传进去的单词,compgen -W是单独生成候选列表(也就是提示你可以补全哪些选项,仅仅是提示)
一般是complete -F
指定补全函数里调用compgen
去匹配当前单词从而生成符合的补全候选列表输出到终端,complete -F function command
,command一般是一个单词,多个单词(命令)都会效有function生成的补全效果
- 自定义补全
complete
然后输入1
2
3
4function _autotab() {
COMPREPLY=(test1 test2 atc abc bhj cd)
}
complete -F _autotab foofoo
再输入一个空格后按tab
即可补全abc atc bhj cd test1 test2
作为候选
但是此时还不完全foo t
后按tab
无法把t
补全成test
且还一直输出数组COMPREPLY
的值,也就是前面说过候选不是补全,但是得人为的关联起来
应该使用compgen来实现补全候选里的单词此时即可正常补全1
2
3
4
5
6
7
8function _autotab() {
local cur
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -W 'test1 test2 atc abc bhj cd' -- ${cur}) )
}
complete -F _autotab foo - 层级命令的补全
此处拿ip命令的两个二级选项一共三种情况来做demolinux里很多这种层级的选项命令,我们用户肯定希望每个部分都候选和补全出可能的所有结果,那么前面第二个demo明显不够用,我们得使用更多变量和写更好的函数来实现1
2ip route showdump
ip address {add|show}
先判断是不是第二部分没有补全,也就是COMP_WORDS的长度为1的时候候选补全route adress
长度和你输入完单词后按空格有关系,先输入下面代码输入下面步骤观察现象1
2
3
4
5
6function _autotab() {
local cur
COMPREPLY=()
COMPREPLY=(${#COMP_WORDS[@]})
}
complete -F _autotab ip上面三个命令输入完成后无论第二个部分输入了否,长度都是21
2
3ip <tab>
ip a<tab>
ip a <tab>1
2
3
4
5
6
7
8
9
10
11
12function _autotab() {
local cur
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
if [ "${#COMP_WORDS[@]}" -eq 2 ];then
COMPREPLY=( $(compgen -W 'route address' -- ${cur}) )
elif [ "${COMP_WORDS[1]}" == address ];then
COMPREPLY=( $(compgen -W 'add show' -- ${cur}) )
fi
}
complete -F _autotab ip
官方函数的使用
安装了bash-completion后/usr/share/bash-completion/bash_completion
里有很多定义好的函数使用起来更加事半功倍,最常见的一个就是下面的这部分了
1 | local cur prev words cword |
其中_get_comp_words_by_ref
是比${COMP_WORDS[$COMP_CWORD]}更好,因为它能更好处理用户在单词中间完成的位置
第一个是光标当前单词,第二个是光标前一个单词,第三个是所有单词的数组,第四个是单词的个数
例如前面的小mode还可以改成下面
1 | function _autotab() { |
补全命令的长选项,下面是grep举例,grep --<tab>
来补全
1 | function t2(){ |
上面这个demo摘自官方的_longopt
函数,完美的补全离不开逻辑紧密的shell函数