在上一篇里咱们定下了给pandoc
写补全脚本的计划:php
pandoc -f
以后,可以补全FORMAT
的内容。先列出实现了第一阶段目标的程序:html
bash# 以pandoc的名字保存下面的程序 _pandoc() { local pre cur opts COMPREPLY=() #pre="$3" #cur="$2" pre=${COMP_WORDS[COMP_CWORD-1]} cur=${COMP_WORDS[COMP_CWORD]} opts="-f -r -t -w -o --output -v --version -h --help" case "$cur" in -* ) COMPREPLY=( $( compgen -W "$opts" -- $cur ) ) esac } complete -F _pandoc -A file pandoc
运行程序的方式:html5
shell$ . ./pandoc # 加载上面的程序 $ pandoc -[Tab][Tab] # 试一下补全能用不
如今我来解释下这个程序。node
bashcomplete -F _pandoc -A file pandoc
是这段代码中最为关键的一行。其实该程序起什么名字都不重要,重要的是要有上面这一行。上面这一行指定bash在遇到pandoc
这个词时,调用_pandoc
这个函数生成补全内容。(叫_pandoc
其实只是出于惯例,并不必定要在前面加下划线)。complete -F
后面接一个函数,该函数将输入三个参数:要补全的命令名、当前光标所在的词、当前光标所在的词的前一个词,生成的补全结果须要存储到COMPREPLY
变量中,以待bash获取。-A file
表示默认的动做是补全文件名,也便是若是bash找不到补全的内容,就会默认以文件名进行补全。git
假设你在键入pandoc -o sth
后,连击两下Tab触发了补全,_pandoc
会被执行,其中:github
$1
的值为pandoc
$2
的值为sth
$3
的值为-o
COMPREPLY
为空(只有cur
以-
开头时,COMPREPLY
才会被填充),因此补全的内容是当前路径下的文件名。你应该看到了,这里我把$2
和$3
都注释掉了。其实shell
bashpre="$3" cur="$2"
和json
bashpre=${COMP_WORDS[COMP_CWORD-1]} # COMP_WORDS变量是一个数组,存储着当前输入全部的词 cur=${COMP_WORDS[COMP_CWORD]}
是等价的。不事后者的可读性更好罢了。segmentfault
最后解释下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
了,那么就须要避免重复添加。
目前的实现代码以下:
bash_pandoc() { local pre cur COMPREPLY=() #pre="$3" #cur="$2" pre=${COMP_WORDS[COMP_CWORD-1]} cur=${COMP_WORDS[COMP_CWORD]} complete_options() { local opts i opts="-f -r -t -w -o --output -v --version -h --help" for i in "${COMP_WORDS[@]}" do if [ "$i" == "-f" -o "$i" == "-r" ] then opts="$opts"" -R -S --filter -p" break fi done for i in "${COMP_WORDS[@]}" do if [ "$i" == "-t" -o "$i" == "-w" ] then opts="$opts"" -s --template --toc" break fi done echo "$opts" } case "$cur" in -* ) COMPREPLY=( $( compgen -W "$(complete_options)" -- $cur ) ) esac } complete -F _pandoc -A file pandoc
注意跟上一个版本相比,这里把原来的opts
变量替换成了complete_options
这个函数的输出。经过使用函数,咱们能够动态地提供补全的来源。好比咱们能够在函数里列出符合特定条件的文件名,做为补全的候选词。
好了,如今是最后一个子任务。大体浏览一下pandoc
的文档,基本上就两类参数:FORMAT
和FILE
。(其它琐碎的咱们就无论了,嘿嘿)
FILE
好办,默认就能够补全路径嘛。那就看看FORMAT
。FORMAT
分两种,一种是读的时候支持的FORMAT
,另外一种是写的时候支持的FORMAT
,这个把文档里面的复制一份,改改就能用了。咱们把读操做支持的FORMAT
叫作READ_FORMAT
,相对的,写操做支持的FORMAT
叫作WRITE_FORMAT
。
补全的来源有了,想一想何时把它放到COMPREPLY
里去。前面补全选项的时候,是经过case语句中-*
来匹配的。可是这里的FORMAT
参数,只在特定选项后面才有意义。因此前面一直坐冷板凳的pre
变量能够上场了。
pre
中存储着光标前一个词。咱们就用一个case语句判断前面是不是-f
或-r
,仍是-t
或-w
。若是符合前面两个组合之一,用compgen
配合READ_FORMAT
或WRITE_FORMAT
生成补全候选词列表,一切就跟处理opts
时同样。因为此时继续参与下一个判断cur
的case语句已经没有意义了,这里直接让它退出函数:
bashREAD_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" case "$pre" in -f|-r ) COMPREPLY=( $( compgen -W "$READ_FORMAT" -- $cur ) ) return 0 ;; -t|-w ) COMPREPLY=( $( compgen -W "$WRITE_FORMAT" -- $cur ) ) return 0 esac
再. ./pandoc
一下,试试看,是否是一切都ok?
诶呀,还有个问题!此次在尝试补全FORMAT
的时候,还会把当前路径下的文件名补全出来。然而这并无什么意义。因此在补全FORMAT
的时候,得把路径补全关掉才行。
问题在于最后一句:complete -F _pandoc -A file pandoc
。目前不论是什么状况,都会补全文件名。因此接下来得限定某些状况下才补全文件名。
第一步是移除最后一行的-A file
,下一步是修改最底下的case语句,变成这样子:
bashcase "$cur" in -* ) COMPREPLY=( $( compgen -W "$(complete_options)" -- $cur ) );; * ) COMPREPLY=( $( compgen -A file )) esac
只有在没有找到对应的补全时,才会调用对路径的补全。
最终版本:
bash_pandoc() { local pre cur COMPREPLY=() #pre="$3" #cur="$2" pre=${COMP_WORDS[COMP_CWORD-1]} cur=${COMP_WORDS[COMP_CWORD]} 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" case "$pre" in -f|-r ) COMPREPLY=( $( compgen -W "$READ_FORMAT" -- $cur ) ) return 0 ;; -t|-w ) COMPREPLY=( $( compgen -W "$WRITE_FORMAT" -- $cur ) ) return 0 esac complete_options() { local opts i opts="-f -r -t -w -o --output -v --version -h --help" for i in "${COMP_WORDS[@]}" do if [ "$i" == "-f" -o "$i" == "-r" ] then opts="$opts"" -R -S --filter -p" break fi done for i in "${COMP_WORDS[@]}" do if [ "$i" == "-t" -o "$i" == "-w" ] then opts="$opts"" -s --template --toc" break fi done echo "$opts" } case "$cur" in -* ) COMPREPLY=( $( compgen -W "$(complete_options)" -- $cur) ) ;; * ) COMPREPLY=( $( compgen -A file )) esac } complete -F _pandoc pandoc
如今补全脚本已经写好了,不过把它放哪里呢?咱们须要找到这样的地方,每次启动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补全脚本。