|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
5 V0 J2 X- |7 b' @. d0 p0 F概述 X) s- A& `% y1 D
bash 自动补全; g( L: i4 i# T2 D& F
- 测试补全的脚本
- 参数自动补全
- 自定义补全$ `# h0 G# m( }3 R x& \0 M) p
zsh 自动补全% ?3 U. `9 V8 \3 h4 u! Z
- 参数自动补全
- 自定义补全) _7 Q- M7 l# v/ W& p, X
总结. a0 K: J. g) d3 q2 B
5 u' H$ q, m- @1 i概述
% e! c: e' T1 p3 g% M虽然CLI(命令行)类型的工具由于其高效,易定制的特性为很多人所喜爱(也包括我自己), 但是,相对于GUI工具,CLI工具给人的直观感觉就是不容易使用,如果看到工具中大量的参数说明后,更让人望而却步。* G4 n+ I& r0 |" [
6 z0 C9 n) |& l
因此,如果在自己命令行工具中加入 自动补全 的功能,就可以极大的提高工具的易用性,还可以保留命令行工具原有的高效。 这里所说的 自动补全 不仅仅是补全那些固定的参数(这些意义不大),更多的是补全动态的内容。% T& ?$ E( J/ a w
" C- F- j7 Y- x" {+ s3 F
本篇主要介绍两种主流的 shell(bash 和 zsh)中,如何实现命令行工具的补全。
) r$ g+ Y* o: q* H
( Z+ ~( B1 r/ h( j4 Wbash 自动补全* }* M/ C, D0 @8 A2 ^9 o
测试补全的脚本7 @! P8 L% B) D8 N
简单编写一个测试脚本用来测试后面的自动补全:
2 @( ~% [& Y" U$ b* g$ i2 w; z
3 E" z" Q6 L! p! ~1 N2 x#!/bin/bash
$ M/ q2 t! F6 Y6 {# ~# filename: cli-test.sh
+ }. v( t& F# j, |. M' P7 N- z. [& @0 O) U5 t+ L1 U2 C- {& `
UPCASE=false
$ Q% A# A. h0 q* xDATE=""
+ g4 e) S P$ ] c
1 T+ ^. Y6 D/ g) k! susage() {
& w! g% y/ g. C% z- y6 ~3 E echo "USAGE:"
& Q6 _3 x* z( C- J, d7 k echo "cli-test <options>"
( I( }! z$ h6 _1 o0 u+ P echo " -h : print help"
) q2 H4 K' ?% }- C" O" U echo " -u : print info upcase". Z( ]& ?1 n( o; F) ]9 E
echo " -p <xxx>: print info"
% Q+ z) v3 A- B echo " -d <xxx>: date in print info"7 a3 J! p' ?% g0 L" u& [
}
( G+ d: Z' v A
% M S- D, ~2 g, }print() {9 N; N; u# h" R
if $UPCASE8 P- d& I( Q, s3 P7 ~5 c
then6 | }' _5 X2 c. s* [* i H4 l% j
echo -n $1 | tr a-z A-Z& F/ Y0 n: {, |: {* @5 _
else
N, V4 b1 Q. J3 i. c( ]- a echo -n $1
. G" z1 D+ [+ R4 d) z( q, e( z fi" s) e4 C5 f m( G) R0 l
$ _$ @' o4 E& H. n9 D* J1 l; F if [ "$DATE" != "" ]
1 A4 w% b& J8 e7 s7 V' H then+ p8 x; T- j! C: O3 o
echo " date: $DATE": A! U0 a5 `1 O) Y* k4 [
else$ e( A }: x& g3 v D4 ~
echo ""
$ E1 B/ p) c. c. Y% O( ^ fi
0 M% S* C* x, K4 P}
2 T2 S- {8 W) e: w( T- C
: e( T! u0 F0 C$ M8 n( \while getopts "hup:d:" opt; do
" ]( \" p3 ~0 i, x. A6 u case "$opt" in# [# H( s+ r" }1 e* ^: Y
h)
3 B$ h4 u; G9 n; N) W usage" N7 h! m4 \( B) l5 A5 x
exit 0
1 u- _( ^, S2 {, U+ T* o4 t ;;! B! H9 X9 D# ]; j+ z& t
u)0 Y- d2 K2 \6 w% x% \) U
UPCASE=true
3 c- t R- a. t6 M4 [' u* A. j0 m ;;
0 x: o/ r; k; q4 P d)* m$ I$ i$ g+ u2 ?% f% q: k$ E
DATE=$OPTARG0 o1 s6 I( m2 g( k' e" q
;;. z# r! J' K# Y) S! \2 k
p) }- G9 Z" J7 X% Y3 G7 [9 h
print $OPTARG
3 A3 p" @0 o* \. S; j) E7 e ;;- T2 v M7 ?, S# n* \* [/ M' C! ?4 V
esac
' k7 ~, k1 [+ w3 c6 [8 }done
6 n4 y/ }2 H9 Q) i& k测试上面的脚本如下:
# d. q0 b6 N. S, F
4 _, O D6 M3 {( q, Y' Z4 r4 Fbash-3.2$ chmod +x cli-test.sh
* S& D; C, _+ o7 x( |+ s$ S* jbash-3.2$ ./cli-test.sh -h
7 k0 v4 M1 s Y+ n2 ]& w% B) YUSAGE:
& @3 t( M7 s6 L3 d9 R q" jcli-test <options>
, \6 O- x* p; k; I -h : print help
; ] `; G2 T: ^/ G -u : print info upcase
2 w) {3 s$ |+ r/ P* y& v y -p <xxx>: print info4 Y2 G9 s7 Y" Z) \' ~ I
-d <xxx>: date in print info2 |& f. p1 p* o( {
bash-3.2$ ./cli-test.sh -p hello5 t+ H0 B9 \/ Y7 r3 |
hellobash-3.2$ ./cli-test.sh -p hello3 f5 k O0 {: d% d4 Y
hello6 U9 ^" }/ m3 k2 R5 V
bash-3.2$ ./cli-test.sh -u -p hello( c* a& R* B$ i' h
HELLO: j, r3 O' Y: v" \* [- d0 S
bash-3.2$ ./cli-test.sh -u -d 2016-10-13 -p hello8 Y l$ }1 w, I7 V: X, ]
HELLO date: 2016-10-13
2 X# w: g* U _% D0 A% T5 k' l% d* `3 A/ I* N# `8 V
9 b! w: k. f6 X& ^# M0 R参数自动补全$ \# \3 S2 j5 j$ Y& |& Z3 K5 N) V
参数的补全一般来说比较简单,因为一个命令行工具的参数一般都是固定的。 下面的参数补全脚本是针对 上面的 测试补全的脚本 cli-test.sh$ I, v3 I3 ^* O* d; L2 [
3 I) x L7 {5 j
_complete_func() {0 D: O* L" |0 B+ m7 C
local cur prev opts base
3 I: |8 S, R, ~. q: \3 Z COMPREPLY=(); m0 t4 r& N& X/ x- O2 c" ?
cur="${COMP_WORDS[COMP_CWORD]}"
) ` g' @ O/ ^ prev="${COMP_WORDS[COMP_CWORD-1]}"
. [4 o4 e( r3 `: r% @3 k
5 w$ f/ P; K8 I1 `1 p opts="-h -u -d -p"
( R/ e3 F: J- o! S7 r/ v7 h4 C. W
) |; D$ Q' C s" | if [[ ${cur} == -* ]] ; then
1 _3 j# H$ B$ a( r2 t$ R COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
! [6 v& V+ E, U6 b8 a1 z return 0
2 V7 T; ^/ d. j, Z6 P/ E& a fi$ t! w1 k) H. u8 B/ l* Z8 w2 D7 y
5 P* S/ k7 I$ Y2 L}$ O) g5 L; u/ n4 f" _
- F n9 I0 n# U, j
complete -F _complete_func cli-test.sh2 o6 |# G( I" k! |8 T
让自动补全脚本生效的方法如下:
! P* l: @+ Y/ Z! e( |
% M) n I5 A0 `# C' _bash-3.2$ source bash_complete # 使自动补全脚本生效
7 m0 ]! V/ y3 Lbash-3.2$ ./cli-test.sh -<TAB><TAB> # 这里输入 - 之后,再输入2次<TAB>就可以把所有能补全的参数列出来3 F H) q% \1 T' H0 I" X
6 Z) Z& C! }% H
" }, O8 H5 \" Y6 g- M3 l
$ A( Z$ A. B$ C: o8 _+ G4 ^4 |/ c自定义补全
, ~; q9 @ X3 j' y9 A- B上面的补全是补全固定的参数,简单,但是用处也不大,用户记不住的其实就是那些会变的参数内容。 下面尝试动态补全 cli-test.sh 的参数 -d 的内容(内容是当前日期以及前3天和后三天的日期) 修改 bash_complete 脚本如下:7 ~( r. ` s) ?7 l: f
( z( i/ i1 q1 s1 Z$ _" w' ?_complete_func() {6 V8 A1 W: |4 ?% r/ N1 k$ C
local cur prev opts base
0 T7 l; c+ L" C( [ COMPREPLY=()
. J* n$ r* z+ e, U8 o cur="${COMP_WORDS[COMP_CWORD]}"$ V$ O+ C! x: J |$ Y+ X( p
prev="${COMP_WORDS[COMP_CWORD-1]}"
5 u. ~/ C2 _2 O
5 N* L# l& o8 I: U if [[ ${cur} == -* ]] ; then
* _# |9 P D- \4 d opts="-h -u -d -p"
/ B2 m3 w* I% f. d$ ^* a* [ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
# I, K5 w( X% j |9 Z; X2 h else4 H5 k! H( _5 J+ k3 w
opts=$( _complete_d_option )- Y/ m6 d7 ~# X+ |
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
# b: F+ `1 T+ V+ ~ fi4 \' O/ v D& x2 O. V0 z
# p$ i8 \" P2 I* V$ Q return 0
- [5 c. G* U; U G2 A G) Z}7 b- w9 V) E/ \, K
) k2 m7 Q; e7 ^1 K! k_complete_d_option() {
H1 T" n5 C* t- ^& L date -v -3d +"%Y-%m-%d"1 [3 t- y& }5 G8 K; y
date -v -2d +"%Y-%m-%d"
5 r+ c+ D3 ]- a date -v -1d +"%Y-%m-%d"
. q* a9 z9 S. g7 U date +"%Y-%m-%d"# Z2 _; x Q2 V
date -v +1d +"%Y-%m-%d". u6 \, B7 D3 M. ]$ k
date -v +2d +"%Y-%m-%d"
" x+ ]* D/ ~9 \& I date -v +3d +"%Y-%m-%d"+ z$ D. w4 Q& u+ G; v I. @
}
% o5 L+ U% I4 ]+ F7 f6 i" `. @3 a2 q# I& F" @' s% b
complete -F _complete_func cli-test.sh! A: v( j9 X& c0 Z* [: @
测试动态补全的效果) g9 a5 M/ Y7 Y& A6 \' d$ H
% l- Z4 @' o2 d1 Z4 V+ \bash-3.2$ source bash_complete # 使自动补全脚本生效7 p5 B5 z4 i- F" R
bash-3.2$ ./cli-test.sh -u -d 2016-10-1<TAB><TAB> # 这是 2016-10-13 执行的结果,其他日子的结果会不一样% K9 w" X" O1 |4 r! g% l k
2016-10-10 2016-10-11 2016-10-12 2016-10-13 2016-10-14 2016-10-15 2016-10-16
; e2 n' S4 P. Q$ o- x1 ~上面就是动态补全,_complete_d_option 函数就是用来实现动态补全的。
/ J' C T4 f6 W, u" D/ T
K6 X. T/ u5 P' u Y; P$ o. \+ j" j* y
zsh 自动补全
# o) R \: P* X相比于bash,zsh 的补全机制更加强大,也更加直观。 同样,下面也通过例子来演示如何在 zsh 中实现上面 bash 中同样的补全功能。
5 H- T) k) l; M4 U
, J! u5 @1 r$ R4 u: Z6 | X' y7 |1 V& u6 K/ O6 k7 R# Q. g; ]
参数自动补全
4 b, G$ ]$ H" y0 z& E G. x相比于 bash 的自动补全脚本,我觉得 zsh 的补全方式更加直观。
& _6 r9 Q V6 u, N0 ~) a1 U- u$ U% \! d% \3 `
#compdef cli-test.sh
S3 ]0 w3 ]$ C# filename: _cli-test.h' I( H/ x' ~2 i( p; D2 Y" f
6 D0 U+ g0 M/ e9 B1 c
_cli_test() {& Z" j1 t3 h/ m) X4 ] {, R
+ g+ R0 S4 V5 j' d
_arguments -C -s -S \
, c) R7 f1 T8 o1 P5 J" w$ ~0 F '-h::' \% S H! M. J% c# h @0 L* ]3 c8 ?
'-u::' \6 E* e: u- S" l' ?% ^
'-d::' \/ [8 @" w' J9 m( [! A$ j
'-p::') F2 e# v" N8 m2 R, H$ x$ b% a9 v
}. y2 D2 N- H% f
' i0 i# s. A* l/ D' g
_cli_test "$@"
) I4 e9 Y& Z$ v% J$ i4 [9 pzsh 中有个 fpath 的内置变量,将自动补全脚本放在 $fpath 中,或者在 $fpath 中创建指向自动补全的脚本的软连接都可以。 下面是我的环境中 fpath 的值
) I$ w# w: `" \, V1 E9 L5 i8 ?- _7 i; t* l" u
$ echo $fpath
* K3 b f! l# [8 Q/usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/5.0.8/functions7 W2 O3 @* d. f: M/ F) T
为了测试 zsh 下自动补全是否有效,我在 fpath 下给自己的自动补全脚本创建了软连接5 u! N/ S; g. i' Z$ e6 }: T
. y1 a( n# Z% R0 `; T/ u1 e8 T: ?$ cd /usr/local/share/zsh/site-functions# }: S$ ? C$ A& i" t# N
$ ln -s ~/projects/bash/autocomp/_cli-test.sh _cli-test.sh
, D1 r; A! w7 m! r+ Q3 K- b测试结果
[5 g6 k. D, @; ?1 _0 v
9 C& @$ F# }3 q$ ./cli-test.sh -<TAB><TAB>7 `# E" Y' I& M
-d -h -p -u1 `% ]: P" z" ?5 x6 J0 d2 n3 q
可以看出,zsh 的补全方法非常简单直观。稍微解释下上面的代码$ G" t1 Y: _* P
' t% p; _3 g$ v+ e' O/ p g2 `
_arguments
" B% W4 U' j* E& U- H3 {7 L这个函数是 zsh 自带的,有点类似 bash 中的 compgen ,但是功能更加强大。
. r- G2 L4 Y# i. g, t* S6 E: J J6 v+ G( B8 _% F: X, }" X
'-h::' \" _, r1 ]" `# I$ j2 y
这里 : 分割的3部分分别是 “待补全的参数:参数的说明:动态补全参数的内容“- }; d; I3 k& A9 d8 G/ }* I
2 l9 Q7 O' Q. H5 A- _4 Q$ b6 ]6 y7 c
! `1 S6 z: R2 O/ C3 M2 s/ ^
自定义补全
$ ?; C: A2 O+ A根据上面的解释,要想动态补全 -d 参数非常简单,只要加个函数,并配置在 -d:: 之后即可5 p' M8 Z+ D( L' x
$ b) ^0 ~/ `) x#compdef cli-test.sh
& E+ K" f9 o& M* z# filename: _cli-test.h9 _8 s8 V; u9 b( a0 g
' @+ s3 ]7 K9 w0 S9 t7 X% `4 ^# N_cli_test() {; y; d V& A8 k' ]; A; T
! p' S0 E/ k |9 |5 [9 L" d$ V _arguments -C -s -S \
6 O- |; Q+ C5 ?" }8 C, B/ @/ o '-h::' \
9 w$ ?% V3 t h '-u::' \. F6 t8 X2 ?# _( @
'-d:auto complete date:__complete_d_option' \
* G2 `8 G- w8 K9 E& F/ f1 r '-p::'
/ ?, o* a8 G7 v2 |; N! S) u}
( z' J% _3 ~6 z: a7 `0 |: M! U
1 y# J( i" T: m% T8 K: b; \. _# V$ j ]0 J__complete_d_option() {. @3 `2 b4 F' ?- N4 a2 J
local expl
* d1 p$ w* c% p6 F5 m. Q: U0 B dates=( `generate_date` )
5 c7 Y% a* j, Z! K) v7 x3 {1 O; L- s* }$ v B7 _, h/ x
_wanted dates expl date compadd $* - $dates! ^% }. L: F; W5 F$ i7 F- `
}
& j0 P: s [8 H, x% q; @
6 e O9 a" g0 r/ l% }6 Agenerate_date() {5 U& y7 A1 K8 s2 U. k1 [9 J5 y
date -v -3d +"%Y-%m-%d"" u y( x2 o5 U
date -v -2d +"%Y-%m-%d": v: e0 ]( N. v
date -v -1d +"%Y-%m-%d"/ L& p" p3 `$ w7 U
date +"%Y-%m-%d"
; P# O2 F( U9 T; w: N1 R date -v +1d +"%Y-%m-%d"
& ^, m4 n3 ~ ]) r/ w, O; B date -v +2d +"%Y-%m-%d"
0 }/ e$ a6 p/ x" z8 r date -v +3d +"%Y-%m-%d"! `1 W. b1 x6 y1 v7 ?
}
1 T) f# E6 s! u1 V P- \6 E: E' i) x: H/ y/ r. h
_cli_test "$@"9 Y. e, Y8 H/ j9 D
测试动态补全的效果2 z+ t" w. X7 |7 l% H0 T& H. a" Y
+ f! ^7 Y1 \" E6 \! K% W; e) c7 M
$ ./cli-test.sh -u -d 2016-10-<TAB><TAB># m. g/ P* y5 u! n4 N3 Z d/ [& B
2016-10-14 2016-10-15 2016-10-16 2016-10-17 2016-10-18 2016-10-19 2016-10-20
8 \" {& ^9 g5 e* i" n4 G
$ [' J& e* Q0 }9 H( I( ^. y- j
2 m, Z/ W6 L! }" [1 a总结- J4 k, M, q3 o$ G$ M) p; A) J
2中shell环境下的自动补全都介绍完了,它们自动补全的机制都不难,只是 zsh 毕竟是新一点的shell,补全方式更加简单易懂。 特别是对于存在子命令和复杂的参数补全,以及参数内容动态补全的情况下,zsh 的机制更加易于维护。3 {. w# S0 r$ P/ W. C
|
|