雖然CLI(命令行)類型的工具因爲其高效,易定製的特性爲不少人所喜好(也包括我本身), 可是,相對於GUI工具,CLI工具給人的直觀感受就是不容易使用,若是看到工具中大量的參數說明後,更讓人望而卻步。shell
所以,若是在本身命令行工具中加入 自動補全 的功能,就能夠極大的提升工具的易用性,還能夠保留命令行工具原有的高效。 這裏所說的 自動補全 不單單是補全那些固定的參數(這些意義不大),更多的是補全動態的內容。bash
本篇主要介紹兩種主流的 shell(bash 和 zsh)中,如何實現命令行工具的補全。函數
簡單編寫一個測試腳本用來測試後面的自動補全:工具
#!/bin/bash # filename: cli-test.sh UPCASE=false DATE="" usage() { echo "USAGE:" echo "cli-test <options>" echo " -h : print help" echo " -u : print info upcase" echo " -p <xxx>: print info" echo " -d <xxx>: date in print info" } print() { if $UPCASE then echo -n $1 | tr a-z A-Z else echo -n $1 fi if [ "$DATE" != "" ] then echo " date: $DATE" else echo "" fi } while getopts "hup:d:" opt; do case "$opt" in h) usage exit 0 ;; u) UPCASE=true ;; d) DATE=$OPTARG ;; p) print $OPTARG ;; esac done
測試上面的腳本以下:測試
bash-3.2$ chmod +x cli-test.sh bash-3.2$ ./cli-test.sh -h USAGE: cli-test <options> -h : print help -u : print info upcase -p <xxx>: print info -d <xxx>: date in print info bash-3.2$ ./cli-test.sh -p hello hellobash-3.2$ ./cli-test.sh -p hello hello bash-3.2$ ./cli-test.sh -u -p hello HELLO bash-3.2$ ./cli-test.sh -u -d 2016-10-13 -p hello HELLO date: 2016-10-13
參數的補全通常來講比較簡單,由於一個命令行工具的參數通常都是固定的。 下面的參數補全腳本是針對 上面的 測試補全的腳本 cli-test.sh命令行
_complete_func() { local cur prev opts base COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-h -u -d -p" if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi } complete -F _complete_func cli-test.sh
讓自動補全腳本生效的方法以下:3d
bash-3.2$ source bash_complete # 使自動補全腳本生效 bash-3.2$ ./cli-test.sh -<TAB><TAB> # 這裏輸入 - 以後,再輸入2次<TAB>就能夠把全部能補全的參數列出來
上面的補全是補全固定的參數,簡單,可是用處也不大,用戶記不住的其實就是那些會變的參數內容。 下面嘗試動態補全 cli-test.sh 的參數 -d 的內容(內容是當前日期以及前3天和後三天的日期) 修改 bash_complete 腳本以下:code
_complete_func() { local cur prev opts base COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [[ ${cur} == -* ]] ; then opts="-h -u -d -p" COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) else opts=$( _complete_d_option ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) fi return 0 } _complete_d_option() { date -v -3d +"%Y-%m-%d" date -v -2d +"%Y-%m-%d" date -v -1d +"%Y-%m-%d" date +"%Y-%m-%d" date -v +1d +"%Y-%m-%d" date -v +2d +"%Y-%m-%d" date -v +3d +"%Y-%m-%d" } complete -F _complete_func cli-test.sh
測試動態補全的效果blog
bash-3.2$ source bash_complete # 使自動補全腳本生效 bash-3.2$ ./cli-test.sh -u -d 2016-10-1<TAB><TAB> # 這是 2016-10-13 執行的結果,其餘日子的結果會不同 2016-10-10 2016-10-11 2016-10-12 2016-10-13 2016-10-14 2016-10-15 2016-10-16
上面就是動態補全,_complete_d_option 函數就是用來實現動態補全的。get
相比於bash,zsh 的補全機制更增強大,也更加直觀。 一樣,下面也經過例子來演示如何在 zsh 中實現上面 bash 中一樣的補全功能。
相比於 bash 的自動補全腳本,我以爲 zsh 的補全方式更加直觀。
#compdef cli-test.sh # filename: _cli-test.h _cli_test() { _arguments -C -s -S \ '-h::' \ '-u::' \ '-d::' \ '-p::' } _cli_test "$@"
zsh 中有個 fpath 的內置變量,將自動補全腳本放在 $fpath 中,或者在 $fpath 中建立指向自動補全的腳本的軟鏈接均可以。 下面是個人環境中 fpath 的值
$ echo $fpath /usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/5.0.8/functions
爲了測試 zsh 下自動補全是否有效,我在 fpath 下給本身的自動補全腳本建立了軟鏈接
$ cd /usr/local/share/zsh/site-functions $ ln -s ~/projects/bash/autocomp/_cli-test.sh _cli-test.sh
測試結果
$ ./cli-test.sh -<TAB><TAB> -d -h -p -u
能夠看出,zsh 的補全方法很是簡單直觀。稍微解釋下上面的代碼
_arguments
這個函數是 zsh 自帶的,有點相似 bash 中的 compgen ,可是功能更增強大。
'-h::' \
這裏 : 分割的3部分分別是 「待補全的參數:參數的說明:動態補全參數的內容「
根據上面的解釋,要想動態補全 -d 參數很是簡單,只要加個函數,並配置在 -d:: 以後便可
#compdef cli-test.sh # filename: _cli-test.h _cli_test() { _arguments -C -s -S \ '-h::' \ '-u::' \ '-d:auto complete date:__complete_d_option' \ '-p::' } __complete_d_option() { local expl dates=( `generate_date` ) _wanted dates expl date compadd $* - $dates } generate_date() { date -v -3d +"%Y-%m-%d" date -v -2d +"%Y-%m-%d" date -v -1d +"%Y-%m-%d" date +"%Y-%m-%d" date -v +1d +"%Y-%m-%d" date -v +2d +"%Y-%m-%d" date -v +3d +"%Y-%m-%d" } _cli_test "$@"
測試動態補全的效果
$ ./cli-test.sh -u -d 2016-10-<TAB><TAB> 2016-10-14 2016-10-15 2016-10-16 2016-10-17 2016-10-18 2016-10-19 2016-10-20
2中shell環境下的自動補全都介紹完了,它們自動補全的機制都不難,只是 zsh 畢竟是新一點的shell,補全方式更加簡單易懂。 特別是對於存在子命令和複雜的參數補全,以及參數內容動態補全的狀況下,zsh 的機制更加易於維護。
來源:http://blog.iotalabs.io/