跟我一起写shell补全脚本(Bash篇)

栏目: Linux · 发布时间: 5年前

内容简介:shell里面补全的影子无处不在,输入命令的时候可以有补全,敲打选项的时候可以有补全,选择文件的时候可以有补全。有些shell甚至支持通过补全来切换版本控制的分支。

如果你是一个重度 shell 用户,一定会关注所用的shell的补全功能。某款shell的补全强弱,也许就是决定你的偏好的第一要素。

shell里面补全的影子无处不在,输入命令的时候可以有补全,敲打选项的时候可以有补全,选择文件的时候可以有补全。有些shell甚至支持通过补全来切换版本控制的分支。由于shell里面可以运行的程序千差万别,shell一般不会内置针特定对某个 工具 的补全功能。与之相对的,shell提供了一些补全用的API,交由用户编写对应的补全脚本。

在这里,我想向大家介绍如何利用提供的API,来编写一个shell补全脚本。由于需要覆盖的内容较多,所以分为Bash和Zsh两篇。也许有fish用户会抱怨,fish又一次被忽略了:D。之所以只有Bash和Zsh的内容,是因为:1. 这两种shell的用户占了shell用户的绝大多数。2. 我没有用过fish,所以对这方面也不了解。希望有人能够锦上添花,写一个fish版本的补全脚本教程。

既然想要写一个shell补全脚本,那么接下来要决定待补全的对象了。这里我选择pandoc作为目标。pandoc是文档转换器中的瑞士军刀,支持主流的各种标记语言,甚至对于PDF和MS Word也有一定程度上的支持。pandoc支持的选项琳琅满目,如果都要实现确实很花时间。所以这里就只实现General options,Reader options,General writer options大部分的内容。不管怎么说,这将会是一个“既不至于简单到让人丧失兴趣,又不至于困难到让人丧失信心”的任务。

安装pandoc的方式见官网上的说明,这里就不赘述了。安装完了之后,man pandoc就能看到各个选项的说明。大体上我们需要实现以下几个目标:

  1. 支持主选项(General options)
  2. 支持子选项(Reader options/General writer options)
  3. 支持给选项提供参数值来源。比如在敲pandoc -f之后,能够补全FORMAT的内容。

好,让我们开始给pandoc写补全脚本吧!

支持主选项

先列出实现了第一阶段目标的程序:

运行程序的方式:

现在我来解释下这个程序。

是这段代码中最为关键的一行。其实该程序起什么名字都不重要,重要的是要有上面这一行。上面这一行指定bash在遇到pandoc这个词时,调用_pandoc这个函数生成补全内容。(叫_pandoc其实只是出于惯例,并不一定要在前面加下划线)。complete -F后面接一个函数,该函数将输入三个参数:要补全的命令名、当前光标所在的词、当前光标所在的词的前一个词,生成的补全结果需要存储到COMPREPLY变量中,以待bash获取。-A file表示默认的动作是补全文件名,也即是如果bash找不到补全的内容,就会默认以文件名进行补全。

假设你在键入pandoc -o sth后,连击两下Tab触发了补全,_pandoc会被执行,其中:

  1. $1的值为pandoc
  2. $2的值为sth
  3. $3的值为-o
  4. 由于COMPREPLY为空(只有cur-开头时,COMPREPLY才会被填充),所以补全的内容是当前路径下的文件名。

你应该看到了,这里我把$2$3都注释掉了。其实

是等价的。不过后者的可读性更好罢了。

最后解释下COMPREPLY=( $( compgen -W "$opts" -- $cur ) )这一行。
opts就是pandoc的主选项列表。

compgen接受的参数和complete差不多。这里它接受一个以IFS分割的字符串"$opts"作为补全的候选项(IFS即shell里面表示分割符的变量,默认是空格或者Tab、换行)。假如没有一项跟当前光标所在的词匹配,那么它返回当前光标所在的词作为结果。(也即是不补全)

实现第一个目标用到的东西就是这么多。接下来就是第二个目标了。
在继续之前,你需要把Bash文档看一遍。若能把其中的一些选项尝试一下就更好了。

支持子选项

接下来的目标是支持Reader options/General writer options。想判断是否需要补全Reader options/General writer options,先要确认输入的词里面是否有-r-f(读),以及-w-t(写)。前面提到的COMP_WORDS就派上用场了。只需要将它迭代一下,查找里面有没有我们需要确认的词。

假设我们已经确认了需要补全子选项,接下来就应该往原来的补全项中添加子选项的内容。需要补全读选项的添加读方面的选项,需要补全写选项的添加写方面的选项。既然补全选项是一个字符串,那么把要添加的字符串接到原来的opts后面就好了。这里要注意一点,假如前面的操作里面已经把某类子选项添加到opts了,那么就需要避免重复添加。

目前的实现代码如下:

注意跟上一个版本相比,这里把原来的opts变量替换成了complete_options这个函数的输出。通过使用函数,我们可以动态地提供补全的来源。比如我们可以在函数里列出符合特定条件的文件名,作为补全的候选词。

支持给选项提供参数值来源

好了,现在是最后一个子任务。大致浏览一下pandoc的文档,基本上就两类参数:FORMATFILE。(其它琐碎的我们就不管了,嘿嘿)

FILE好办,默认就可以补全路径嘛。那就看看FORMATFORMAT分两种,一种是读的时候支持的FORMAT,另一种是写的时候支持的FORMAT,这个把文档里面的复制一份,改改就能用了。我们把读操作支持的FORMAT叫做READ_FORMAT,相对的,写操作支持的FORMAT叫做WRITE_FORMAT

补全的来源有了,想想什么时候把它放到COMPREPLY里去。前面补全选项的时候,是通过case语句中-*来匹配的。但是这里的FORMAT参数,只在特定选项后面才有意义。所以前面一直坐冷板凳的pre变量可以上场了。

pre中存储着光标前一个词。我们就用一个case语句判断前面是否是-f-r,还是-t-w。如果符合前面两个组合之一,用compgen配合READ_FORMATWRITE_FORMAT生成补全候选词列表,一切就跟处理opts时一样。由于此时继续参与下一个判断cur的case语句已经没有意义了,这里直接让它退出函数:

. ./pandoc一下,试试看,是不是一切都ok?

诶呀,还有个问题!这次在尝试补全FORMAT的时候,还会把当前路径下的文件名补全出来。然而这并没有什么意义。所以在补全FORMAT的时候,得把路径补全关掉才行。

问题在于最后一句:complete -F _pandoc -A file pandoc。目前不管是什么情况,都会补全文件名。所以接下来得限定某些情况下才补全文件名。

第一步是移除最后一行的-A file,下一步是修改最底下的case语句,变成这样子:

只有在没有找到对应的补全时,才会调用对路径的补全。

最终版本:

最后的问题

现在补全脚本已经写好了,不过把它放哪里呢?我们需要找到这样的地方,每次启动bash的时候都会自动加载里面的脚本,不然每次都要手动加载,那可吃不消。

.bashrc是一个(不推荐的)选择,不过好在bash自己就提供了在启动时加载补全脚本的机制。

如果你的系统有这样的文件夹:/etc/bash_completion.d,那么你可以把补全脚本放到那。这样每次bash启动的时候就会加载你写的文件。

如果你的系统里没有这个文件夹,你需要查看下/etc/bash_completion这个文件。bash启动的时候,会执行. /etc/bash_completion,你可以把你的补全脚本放在这个地方。

正如许多配置文件一样,凡是有/etc版本的也对应的~/.版本。有/etc/bash_completion,自然也有~/.bash_completion。如果你只想让自己使用这个补全脚本,或者没有root权限,可以放在~/.bash_completion

Bash补全脚本的内容就是这么多……请期待下一篇的Zsh补全脚本。


以上所述就是小编给大家介绍的《跟我一起写shell补全脚本(Bash篇)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法小时代

算法小时代

Serge Abiteboul、Gilles Dowek / 任铁 / 人民邮电出版社 / 2017-10-1 / 39.00元

算法与人工智能是当下最热门的话题之一,技术大发展的同时也引发了令人忧心的技术和社会问题。本书生动介绍了算法的数学原理和性质,描述了算法单纯、本质的功能,分析了算法和人工智能对人类社会现状及未来发展的影响力及其成因。一起来看看 《算法小时代》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具