在上一篇裏咱們定下了給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補全腳本。