命令行自动补全原理- 概述
- bash 自动补全
- 测试补全的脚本
- 参数自动补全
- 自定义补全
- zsh 自动补全
- 参数自动补全
- 自定义补全
- 总结
概述
虽然CLI(命令行)类型的工具由于其高效,易定制的特性为很多人所喜爱(也包括我自己), 但是,相对于GUI工具,CLI工具给人的直观感觉就是不容易使用,如果看到工具中大量的参数说明后,更让人望而却步。因此,如果在自己命令行工具中加入
自动补全 的功能,就可以极大的提高工具的易用性,还可以保留命令行工具原有的高效。 这里所说的
自动补全 不仅仅是补全那些固定的参数(这些意义不大),更多的是补全动态的内容。本篇主要介绍两种主流的 shell(bash 和 zsh)中,如何实现命令行工具的补全。
bash 自动补全
测试补全的脚本
简单编写一个测试脚本用来测试后面的自动补全:
#!/bin/bash# filename: cli-test.shUPCASE=falseDATE=""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 $UPCASEthen echo -n $1 | tr a-z A-Zelseecho -n $1fiif [ "$DATE" != "" ]thenecho " date: $DATE"elseecho ""fi}while getopts "hup:d:" opt; docase "$opt" inh)usageexit 0;;u)UPCASE=true;;d)DATE=$OPTARG;;p)print $OPTARG;;esacdone测试上面的脚本如下:
bash-3.2$ chmod +x cli-test.shbash-3.2$ ./cli-test.sh -hUSAGE:cli-test <options>-h: print help-u: print info upcase-p <xxx>: print info-d <xxx>: date in print infobash-3.2$ ./cli-test.sh -p hellohellobash-3.2$ ./cli-test.sh -p hellohellobash-3.2$ ./cli-test.sh -u -p helloHELLObash-3.2$ ./cli-test.sh -u -d 2016-10-13 -p helloHELLO date: 2016-10-13参数自动补全
参数的补全一般来说比较简单,因为一个命令行工具的参数一般都是固定的。 下面的参数补全脚本是针对 上面的 测试补全的脚本
cli-test.sh_complete_func() {local cur prev opts baseCOMPREPLY=()cur="${COMP_WORDS[COMP_CWORD]}"prev="${COMP_WORDS[COMP_CWORD-1]}"opts="-h -u -d -p"if [[ ${cur} == -* ]] ; thenCOMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )return 0fi}complete -F _complete_func cli-test.sh让自动补全脚本生效的方法如下:
bash-3.2$ source bash_complete# 使自动补全脚本生效bash-3.2$ ./cli-test.sh -<TAB><TAB> # 这里输入 - 之后,再输入2次<TAB>就可以把所有能补全的参数列出来自定义补全
上面的补全是补全固定的参数,简单,但是用处也不大,用户记不住的其实就是那些会变的参数内容。 下面尝试动态补全
cli-test.sh 的参数 -d 的内容(内容是当前日期以及前3天和后三天的日期) 修改 bash_complete 脚本如下:
_complete_func() {local cur prev opts baseCOMPREPLY=()cur="${COMP_WORDS[COMP_CWORD]}"prev="${COMP_WORDS[COMP_CWORD-1]}"if [[ ${cur} == -* ]] ; thenopts="-h -u -d -p"COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )elseopts=$( _complete_d_option )COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )fireturn 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测试动态补全的效果
bash-3.2$ source bash_complete# 使自动补全脚本生效bash-3.2$ ./cli-test.sh -u -d 2016-10-1<TAB><TAB> # 这是 2016-10-13 执行的结果,其他日子的结果会不一样2016-10-102016-10-112016-10-122016-10-132016-10-142016-10-152016-10-16上面就是动态补全,_complete_d_option 函数就是用来实现动态补全的。
zsh 自动补全
相比于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 expldates=( `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-142016-10-152016-10-162016-10-172016-10-182016-10-192016-10-20总结
2中shell环境下的自动补全都介绍完了,它们自动补全的机制都不难,只是 zsh 毕竟是新一点的shell,补全方式更加简单易懂。 特别是对于存在子命令和复杂的参数补全,以及参数内容动态补全的情况下,zsh 的机制更加易于维护。
本文永久更新链接地址