写linux脚本是怎么写PATH,跟我一起写shell补全脚本(Zsh篇)

绝大部分日常使用Linux和OS X的程序员都会选择zsh作为自己的shell环境,毕竟对比于bash,zsh的便利性/可玩性要胜出很多,同时它又能兼容bash大多数的语法。不过相对而言,zsh补全脚本要比bash补全脚本要难写。zsh提供了非常多的补全的API,而且这些API功能有不少重叠的地方,掌握起来并不容易。不像bash,你只需记住三个API(compgen,complete,compopt)就能实现整个补全脚本。

这篇的任务跟上一篇的一样,需要实现一个针对pandoc的补全脚本,囊括下面三个目标:

支持主选项(General options)

支持子选项(Reader options/General writer options)

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

何处安放脚本

在开始之前,需要说明下放置zsh脚本的地方,这样我们才能让接下来写的补全脚本发挥效力。

zsh在启动时会加载$fpath路径下的脚本文件。试试echo $fpath来看看这个变量的值。接下来我们可以把补全脚本放到$fpath的路径下,或者创建一个新的在$fpath路径中的目录:

mkdir ~/.fpath

在~/.zshrc中添加fpath=($HOME/.fpath $fpath)

重启zsh

当我们把自己写的补全脚本放好后,每次zsh一启动,就会加载它。不过总不能每次修改完脚本后,都重启一次zsh吧。如果只是单纯更新补全脚本,可以执行unfunction _pandoc && autoload -U _pandoc,zsh就会重新加载补全脚本了。(其中_pandoc是补全脚本的名字)

支持主选项

还是跟上一篇一样,先解释一个实现第一个目标的程序,带各位入门:

zsh#compdef pandoc

# 把它命名为_pandoc,保存在$fpath路径下

_arguments \

{-f,-r}'[-f FORMAT, -r FORMAT, Specify input format]' \

{-t,-w}'[-t FORMAT, -w FORMAT, Specify output format]' \

{-o,--output}'[-o FILE, --output=FILE, Write output to FILE instead of stdout]' \

{-h,--help}'[Show usage message]' \

{-v,--version}'[Print version]' \

'*:files:_files'

就像bash的complete,zsh也有一个相对的表示补全的API,就是compdef。zsh补全脚本以#compdef tools开头,表示该文件是针对tools的补全脚本。当然你也可以像bash一样,直接compdef _function tools来指定tools的补全函数。

zsh补全API的第一梯队是_alternative、_arguments、_describe、_gnu_generic、_regex_arguments。它们直接提供补全的来源。这些API的概述见https://github.com/zsh-users/zsh-completions/blob/master/zsh-completio...。由于_describe能做的_arguments也能做,_gnu_generic是为GNU拓展的命令参数准备的,_regex_arguments就是正则匹配版的_arguments,所以只要记住_arguments和_alternative就够用了。

_arguments接受一连串的选项字符串,每个字符串代表一个选项。另外你还可以通过一些选项指定补全上的细节。举-s为例:假设你的工具支持-a -b两个选项,也支持-ab的方式来同时指定两个选项。如果没给_arguments提供-s的选项,那么zsh是不会补全出-ab,因为并不存在选项-ab。而提供了-s后,_arguments才允许你在已经输入-a的情况下,补全出-ab。

选项字符串的格式是这样的:-x[description]:message:action。你也可以写做{-x,-y}[description]:message:action形式,表示-x和-y是等价的写法。

最后一行'*:files:_files'表示,如果找不到匹配的候选词,就补全文件名。

到目前为止,实现第一阶段目标的脚本所需的知识点已经讲解完毕。

_arguments有一个限制,它要求选项的名字符合某些特殊格式,比如以-、+、=等字符开头(所以才叫_arguments嘛)。如果你的工具接受add、remove之类的子命令,就需要用到_alternative。

_alternative支持的选项字符串格式跟_arguments很像,比如

_arguments \

{-t,-w}'[-t FORMAT, -w FORMAT, Specify output format]'

等价于

_alternative \

'writer:writer options:((-t\:"-t FORMAT, -w FORMAT, Specify output format" -w\:"-t FORMAT, -w FORMAT, Specify output format"))'

支持子选项

所谓的支持子选项,就是在某些选项存在的情况下,增加多一些选项。所以,我们所要做的,就是检查当前输入的命令行参数中是否存在某些参数,如果存在,增加新的选项。这一步可以分解成两个步骤,第一个是检查某些参数是否存在,第二个是增加新的选项。

之前写bash补全脚本的时候,是通过遍历某个存储有当前输入的常量数组,来检查某些参数是否存在。在网上搜索一番后,我发现zsh也有同样的常量数组,就叫做words,正好是bash那个的小写哈。那么接下来就是zsh的语法知识了:

zshif [[ ${words[(i)-f]} -le ${#words} ]] || [[ ${words[(i)-r]} -le ${#words} ]]

then

# 修改补全候选列表

fi

if [[ ${words[(i)-t]} -le ${#words} ]] || [[ ${words[(i)-w]} -le ${#words} ]]

then

# 修改补全候选列表

fi

这里用到一点zsh特有的下标语法,相当于index()。

那么下面是第二步,该怎么修改补全候选列表呢?如果直接用_arguments指定新的补全列表,会覆盖掉前面指定的补全列表。当然也可以把前面的补全列表复制一份,并添加新的选项,用它覆盖掉原来的补全列表。不过这么一来代码就不好看了。

想来zsh应该提供了对应的API的。果不其然,有一个_values可以用来干这事。_values功能跟_arguments差不多,而且它接受的选项列表是添加到原有的选项列表中的,而不是覆盖。所以最后的代码是这样的:

zshif [[ ${words[(i)-f]} -le ${#words} ]] || [[ ${words[(i)-r]} -le ${#words} ]]

then

_values 'reader options' \

'-R[Parse untranslatable HTML codes and LaTeX as raw]' \

'-S[Produce typographically correct output]' \

'--filter[Specify an executable to be used as a filter]' \

'-p[Preserve tabs instead of converting them to spaces]'

fi

if [[ ${words[(i)-t]} -le ${#words} ]] || [[ ${words[(i)-w]} -le ${#words} ]]

then

_values 'writer options' \

'-s[Produce output with an appropriate header and footer]' \

'--template[Use FILE as a custom template for the generated document]' \

'--toc[Include an automatically generated table of contents]'

fi

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

最后一步是给-f和-r这两个选项提供读操作支持的FORMAT参数,给-t和-w这两个选项提供写操作支持的FORMAT参数。

在Bash篇的实现中,我们检查上一个词的值,如果它是-f或-r,那么对当前词补全读操作的FORMAT参数。对写操作的选项也同理。

在zsh中,我们可以用一个特殊的Action:->VALUE来实现。

->VALUE这样的Action会把$state变量设置成VALUE,接下来靠一个case语句块就能根据当前陷入的状态进行对应的参数补全。

那么该如何补全FORMAT参数列表呢?这里可以用上_multi_parts。

_multi_parts第一个参数是分隔符,之后接受一组候选词或一个候选词数组作为候选词列表。例如_multi_parts , a,b,c,就会生成a b c这个补全候选列表。

这里的FORMAT变量直接使用上一章的$READ_FORMAT和$WRITE_FORMAT。

我试了一下,如果把FORMAT变量当做字符串传递过去的话,其间的空格会被转义,导致无法分隔开来,于是就把它们改写成数组的形式。

另外,由于补全FORMAT参数时,不再需要补全选项了。所以把补全FORMAT参数的部分提到补全子选项的前面,并在补全后直接退出程序的执行。

最终完成的代码如下:

zsh#compdef pandoc

local READ_FORMAT WRITE_FORMAT

READ_FORMAT='(native json markdown markdown_strict markdown_phpextra

markdown_github textile rst html docbook opml mediawiki haddock latex)'

WRITE_FORMAT='(native json plain markdown markdown_strict

markdown_phpextra markdown_github rst html html5 latex beamer context

man mediawiki textileorg textinfo opml docbook opendocument odt docx

rtf epub epub3 fb2 asciidoc slidy slideous dzslides revealjs s5)'

_arguments \

{-f,-r}'[-f FORMAT, -r FORMAT, Specify input format]: :->reader' \

{-t,-w}'[-t FORMAT, -w FORMAT, Specify output format]: :->writer' \

{-o,--output}'[-o FILE, --output=FILE, Write output to FILE instead of stdout]' \

{-h,--help}'[Show usage message]' \

{-v,--version}'[Print version]' \

'*:files:_files'

case "$state" in

reader )

_multi_parts ' ' $READ_FORMAT && return 0

;;

writer )

_multi_parts ' ' $WRITE_FORMAT && return 0

esac

if [[ ${words[(i)-f]} -le ${#words} ]] || [[ ${words[(i)-r]} -le ${#words} ]]

then

_values 'reader options' \

'-R[Parse untranslatable HTML codes and LaTeX as raw]' \

'-S[Produce typographically correct output]' \

'--filter[Specify an executable to be used as a filter]' \

'-p[Preserve tabs instead of converting them to spaces]'

fi

if [[ ${words[(i)-t]} -le ${#words} ]] || [[ ${words[(i)-w]} -le ${#words} ]]

then

_values 'writer options' \

'-s[Produce output with an appropriate header and footer]' \

'--template[Use FILE as a custom template for the generated document]' \

'--toc[Include an automatically generated table of contents]'

fi

后话

由于zsh的补全功能实在强大,而这篇文章只是简略地讲讲如何写出一个zsh补全脚本,有许多zsh的补全机制都没能提到。所以补充一些写zsh补全脚本的资料,如果对这方面有兴趣可以继续跳坑:

zsh-completions项目上的教程。这是我见过的最详尽的zsh补全脚本教程。

/usr/share/zsh/functions/Completion 也许你能从相似的命令的补全脚本中汲取灵感。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值