|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
1 M4 ^) @0 W5 [# Q
概述
2 d; V6 T0 {1 x" Gbash 自动补全. m7 w8 R$ w9 d! e
- 测试补全的脚本
- 参数自动补全
- 自定义补全% \9 F; N" l$ S8 |3 a, l
zsh 自动补全1 T( C# [- X4 ^( R! p) b* V+ ?! a
- 参数自动补全
- 自定义补全" }' t2 D* H! T
总结 c9 G: j, G. m. e+ C! y
- U; k0 Y9 j4 I. V1 n& [9 V: j0 x
概述! C2 R; Z8 q! j- t7 _
虽然CLI(命令行)类型的工具由于其高效,易定制的特性为很多人所喜爱(也包括我自己), 但是,相对于GUI工具,CLI工具给人的直观感觉就是不容易使用,如果看到工具中大量的参数说明后,更让人望而却步。
5 f, P6 p3 ^* s' W8 r1 r8 ]
$ u9 n/ [7 t; `! x" P) `因此,如果在自己命令行工具中加入 自动补全 的功能,就可以极大的提高工具的易用性,还可以保留命令行工具原有的高效。 这里所说的 自动补全 不仅仅是补全那些固定的参数(这些意义不大),更多的是补全动态的内容。9 L) n- w$ C6 c0 T! F% E% p
* |5 B( R' H' W3 U7 j本篇主要介绍两种主流的 shell(bash 和 zsh)中,如何实现命令行工具的补全。
" O& o9 a( e1 T* M. y
5 C( {' K9 k1 lbash 自动补全3 h4 S. _$ j/ `$ U; L: g2 K( F
测试补全的脚本
; a/ V$ x( R/ \( ~* h简单编写一个测试脚本用来测试后面的自动补全:4 Y7 _, Z; r( _- V+ @. e
/ _2 ~: t0 j- U" p! M3 ?
#!/bin/bash5 }5 ?4 L0 V4 A
# filename: cli-test.sh
0 X$ i0 B+ I4 K+ V) h
, e" V+ a; R) X! T2 x0 RUPCASE=false
' ~4 n4 Z% o7 c7 H! s+ O3 m6 UDATE="" r7 p4 N# f& R1 \0 |! E/ f: ~7 F$ u
4 T8 \$ c1 W+ A) K" K. Yusage() {
3 i- f$ z) ? e$ m echo "USAGE:"
5 G$ t# S/ `. m$ u echo "cli-test <options>"
- p7 ~- |! ^7 V echo " -h : print help"
' ]; g a0 u) @+ @ echo " -u : print info upcase"
# `8 o) y1 ]6 A8 W3 I echo " -p <xxx>: print info"
/ d- ?& p/ J$ k2 Z; o. [5 y echo " -d <xxx>: date in print info"
; g" i) z/ B. x+ t% A7 A. ~% k}
2 h/ T, l8 K* L% g! h& {6 |3 q1 o6 s) u7 N% V$ Z5 [1 F0 r( {
print() {, \4 |8 r9 h) s I% k
if $UPCASE+ E$ |1 Y1 q4 s6 k8 g3 ^2 F, h) v
then! {- z4 M& }0 c/ G* I* Z4 u
echo -n $1 | tr a-z A-Z7 H n: \0 w/ e& ], d) N
else
' f& J! G$ ?# U# Q- Q* X echo -n $1: y/ p1 J& {7 v. ^5 L
fi1 D) I1 n+ S8 w. d& D
$ \$ U/ c: u6 L4 B% F0 A5 j8 w% L if [ "$DATE" != "" ]
! _0 P; w c' o then1 I$ a ^- e8 ^& z
echo " date: $DATE"
; I2 A) w: u' }2 T6 k% W else
5 ^5 z2 t/ B% k R echo ""
8 F. ^# C3 s7 d fi6 X! `2 r! |9 w( l# F( J2 A0 h. c
}
9 F% f; w! k. W: H) ]1 E' G, w2 a6 F! [: \
while getopts "hup:d:" opt; do$ n$ y( G% }% Y- D+ i- z' M
case "$opt" in
+ x: l; L/ O2 J# i: }% x. v$ J h)# X) H) `. w" d
usage
# j5 e! ^2 U3 n. L exit 0( V' Z$ R3 l9 }) B5 R
;;: R* l+ z! T5 t/ l, a: l" `
u)
) b1 T! Y$ Q* s, K6 k UPCASE=true
2 d2 ? m; R" V+ S( m: h/ H ;;
; g, j3 L1 o9 b d)2 v7 @/ ?0 l# Z- \9 X, i! Z
DATE=$OPTARG
e% k5 J# k" @/ @ P/ d ;;
2 _8 `" L, U R: [- | p)0 q! j! L; {% A9 f. t7 {) J9 _
print $OPTARG
. Y; A: l+ {9 S/ H2 ^ R! y5 p ;;6 Z/ y5 Z! ?4 z( B8 T* m8 G4 `; m1 _
esac: w5 J+ k* J5 ]4 Q6 O0 [0 X
done
( _& w ^5 a* T9 O( |测试上面的脚本如下:
- N: y: G* s0 d( A
8 _* C% {" B" a! y% a% Ibash-3.2$ chmod +x cli-test.sh
! N# @. ]$ [: A7 r: x! zbash-3.2$ ./cli-test.sh -h
# O$ t' A$ {( w' ?( M( M. QUSAGE:
! }- i, X5 _4 ` zcli-test <options>) U6 v5 e: J; I4 S7 n' x
-h : print help. X3 p# ~. ]9 b- X4 l* u4 C
-u : print info upcase
, K8 \) \. R3 H8 L$ { -p <xxx>: print info/ f; b. U+ n& Q! y; t+ F, l
-d <xxx>: date in print info$ v$ p( } H" ?- _+ T' f
bash-3.2$ ./cli-test.sh -p hello5 N2 e; N8 [& z0 |
hellobash-3.2$ ./cli-test.sh -p hello8 U9 e/ m/ k1 F: [ f& X, _
hello
+ o* q" t) ?3 f4 P+ hbash-3.2$ ./cli-test.sh -u -p hello. h0 B/ t: w- E. `
HELLO
0 m( S2 f% g7 N) vbash-3.2$ ./cli-test.sh -u -d 2016-10-13 -p hello
2 X' |: m- |7 f' ^8 J# K- VHELLO date: 2016-10-13
& _7 J. o. T. d$ t4 w* i# G/ h% ?3 h* p2 \- ^
1 c& E6 ]/ n1 \/ p G/ y- f
参数自动补全
3 ^/ g- ?4 _+ t4 g8 u参数的补全一般来说比较简单,因为一个命令行工具的参数一般都是固定的。 下面的参数补全脚本是针对 上面的 测试补全的脚本 cli-test.sh
- [, ?+ t$ K8 ~# i
) v8 t2 B/ d4 c1 r; A_complete_func() {
- i1 O; o4 u4 j% k8 n) c local cur prev opts base
# e3 ^; m, l! W- _" P COMPREPLY=()
) E( |: ~8 k/ n- o I0 l2 w5 W* | cur="${COMP_WORDS[COMP_CWORD]}"
; _* X# r! Z- M6 J prev="${COMP_WORDS[COMP_CWORD-1]}"# Z5 ^7 E7 M9 |2 V" _+ T6 j
7 ^( S5 X7 t3 i+ Y! } opts="-h -u -d -p"
) d l- A8 E1 g, w
( o, Y7 r! G* R if [[ ${cur} == -* ]] ; then
, w( |" }6 E5 V- g( R COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )% }+ E/ [. j. |! `4 u
return 0
5 k* _3 o; f* W, H) L+ D* o6 p8 m# Z2 } fi
2 _) |# L) F7 T% O$ l" p( T" t7 m& D: v( h9 B/ |
}( A, b( @* m$ J# w1 B& ?! ]
8 F5 n4 \' a/ C3 U; n* {6 [
complete -F _complete_func cli-test.sh
( h1 M1 k2 o8 q' |$ v% L. x让自动补全脚本生效的方法如下:
, Z% J# n# U) t' |0 i) l) u; P p( J1 Y
bash-3.2$ source bash_complete # 使自动补全脚本生效
$ d3 r& S% S5 B! M1 }+ ^8 Rbash-3.2$ ./cli-test.sh -<TAB><TAB> # 这里输入 - 之后,再输入2次<TAB>就可以把所有能补全的参数列出来
( n& W8 [5 }. {0 F# z' b1 w3 i @& C# _9 H, ]# N8 w' ^
* X5 y9 H8 A/ w$ o
1 z- X8 ?5 n1 E$ F( g自定义补全
% k4 U- i; P& S) V" j$ w1 X上面的补全是补全固定的参数,简单,但是用处也不大,用户记不住的其实就是那些会变的参数内容。 下面尝试动态补全 cli-test.sh 的参数 -d 的内容(内容是当前日期以及前3天和后三天的日期) 修改 bash_complete 脚本如下:& Y1 E% u+ O" F
" X# u1 ]6 ^! R/ \: g_complete_func() {! J/ S' W5 X7 z: g) ?# N
local cur prev opts base
0 l i% P0 {9 s* R COMPREPLY=()
9 A2 T; u# ~* r8 ^ cur="${COMP_WORDS[COMP_CWORD]}"
* x# [8 z5 c+ j! c, c7 [ prev="${COMP_WORDS[COMP_CWORD-1]}"+ j* a. {# t2 V( S4 Y
o) T- X* t4 u8 d8 R7 P if [[ ${cur} == -* ]] ; then1 H j/ G$ E# L
opts="-h -u -d -p"
% P. i8 x0 e! ^& A- l1 |0 t, a COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )- F( C- ^9 R2 `
else# [9 o! v1 ~) S3 w9 P
opts=$( _complete_d_option )
3 x+ f. l7 ] w. u& l* p% L COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )7 Z u9 T; w0 I9 L) ~# `/ Q8 h
fi# b1 ?- Q: R3 n
$ b$ x8 A, d. Y/ h0 e4 _ return 06 V1 m' ]! S% C5 P7 r" _
}6 K. r& F u! E( ]0 e
' A9 o- s) \, ?1 `_complete_d_option() {0 Y9 z( P6 ?0 B& P( [9 e2 e
date -v -3d +"%Y-%m-%d"" x5 E9 Z2 O; t8 K2 H
date -v -2d +"%Y-%m-%d"2 X' ?/ y c: _( t, w. B* ~7 x7 q
date -v -1d +"%Y-%m-%d"
* b2 y' j/ I/ h date +"%Y-%m-%d"1 h2 K# H, y/ e/ i% y+ N8 O+ G
date -v +1d +"%Y-%m-%d"
8 q2 B( `5 N7 D3 l date -v +2d +"%Y-%m-%d"
* L2 V0 J7 D7 _5 f date -v +3d +"%Y-%m-%d"
% ^. {0 c m$ ?0 I L" C}- _: }* ^$ g# a4 @
- D* X, [* S+ \& V( t3 wcomplete -F _complete_func cli-test.sh
1 k! o7 N: F" s测试动态补全的效果7 k6 f! a* k" N8 P
7 i# s$ E2 I- I X- Y/ dbash-3.2$ source bash_complete # 使自动补全脚本生效9 X# }" w J0 G: S( r: b; B9 m
bash-3.2$ ./cli-test.sh -u -d 2016-10-1<TAB><TAB> # 这是 2016-10-13 执行的结果,其他日子的结果会不一样
# M0 D1 k- n9 b# n$ F; T2016-10-10 2016-10-11 2016-10-12 2016-10-13 2016-10-14 2016-10-15 2016-10-168 ^4 a& h+ D! ] u3 k
上面就是动态补全,_complete_d_option 函数就是用来实现动态补全的。7 }! d' `, a' M/ i: W& N0 J* v f. l
D& o4 |6 G; s; p' w- m' f
: g5 a. ]2 ^( Y. }6 E7 d
zsh 自动补全( G$ {: x, A& Q. D
相比于bash,zsh 的补全机制更加强大,也更加直观。 同样,下面也通过例子来演示如何在 zsh 中实现上面 bash 中同样的补全功能。
$ M$ _* o; C& Z" d* y0 s: i4 ?9 D1 ?+ M
G1 V' c9 {. W' @参数自动补全3 E. u; ]% H9 D! U
相比于 bash 的自动补全脚本,我觉得 zsh 的补全方式更加直观。
9 O& s3 j" V1 e/ ]) f8 @9 {6 P' ^; T% u+ Z9 _( e
#compdef cli-test.sh, a* w+ u. q3 E2 R1 B
# filename: _cli-test.h: Y O6 ~& N- U" Y
/ V2 j* c, X" A' p# d9 u- _
_cli_test() {6 V1 I2 m9 f' S& h
+ _% P. M% a4 C9 I$ @+ K _arguments -C -s -S \
/ M# o8 G6 I9 r- j8 G* { '-h::' \
4 H- p; C! R, @' n3 ? '-u::' \
, m" m" L# O% ?6 b '-d::' \
8 g$ B: W. m2 F '-p::'" r W% [1 V. W% T/ }7 X
}0 L& e( c1 y! x5 Z/ O1 V' h5 {( `
3 C0 m2 ^0 J! w* m9 g_cli_test "$@"
- H* T+ _$ \6 I+ ?, Yzsh 中有个 fpath 的内置变量,将自动补全脚本放在 $fpath 中,或者在 $fpath 中创建指向自动补全的脚本的软连接都可以。 下面是我的环境中 fpath 的值
/ p* Q3 ~6 g, C# Y0 T3 O* Q7 h7 R8 h8 W8 ?# c1 |
$ echo $fpath. f$ p6 ]+ I c6 F! ~
/usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/5.0.8/functions0 k" Q6 n1 l9 X/ S2 `
为了测试 zsh 下自动补全是否有效,我在 fpath 下给自己的自动补全脚本创建了软连接
& v+ l/ @) ^ s% ^
8 r8 f4 l* e, V9 `$ [2 D3 f3 J$ cd /usr/local/share/zsh/site-functions
7 u+ D/ z0 b# ]- q2 b% J2 F$ ln -s ~/projects/bash/autocomp/_cli-test.sh _cli-test.sh
) X/ Q/ U6 X& k9 G( l8 A( {测试结果
8 K4 v, W+ L' ~3 i
, u7 Y: F0 |# j1 H# a& G4 g$ ./cli-test.sh -<TAB><TAB>
+ _% {2 u4 H& x-d -h -p -u. j# ^8 M- l! {7 Q& f) F! M
可以看出,zsh 的补全方法非常简单直观。稍微解释下上面的代码: I1 U0 B- E* b
2 o9 k2 f! n9 ^" d- p' r) h9 E
_arguments! L9 E& o% k0 u) L
这个函数是 zsh 自带的,有点类似 bash 中的 compgen ,但是功能更加强大。
+ v. L2 _+ i" A8 j+ l A" w. F& P! E' U2 n
'-h::' \
4 W0 Y4 \5 S8 \% R1 x$ Y这里 : 分割的3部分分别是 “待补全的参数:参数的说明:动态补全参数的内容“4 P6 I9 g. f8 o
+ |: K6 K- U7 G* u
/ H* k5 S" F1 Y1 {7 v) o N( ^, V自定义补全
0 I2 H: a9 |# @* a根据上面的解释,要想动态补全 -d 参数非常简单,只要加个函数,并配置在 -d:: 之后即可
+ X r: F& \9 G3 J2 Y$ |! I! Z/ o/ l" e
#compdef cli-test.sh4 e( T* A, }! }' H! k; N* u/ ?
# filename: _cli-test.h$ H8 g, @+ M$ V* l
8 T @, n$ H) A% N, F5 y
_cli_test() {! [8 D: z$ x0 v% [
8 d9 U7 h7 l$ |, z _arguments -C -s -S \8 u4 i, N2 L2 L$ `2 N9 Q2 H. }7 B
'-h::' \
- s; e7 R0 J- j1 i) k, B '-u::' \1 O; t/ Z+ A3 ?. c6 S( N9 w2 }6 S
'-d:auto complete date:__complete_d_option' \! _+ u3 s2 U% X0 W$ W! D, ]9 U) X
'-p::'( X$ Z2 z0 ]& Q% b
}
9 c" _/ a, c& ]; F( G
6 R9 p; O. w$ Y: d__complete_d_option() {
# S% q. Q& m! m0 H7 L2 R3 J local expl) F8 T# i2 k' H0 l* b
dates=( `generate_date` )2 N7 G7 @7 F F: B7 }9 V# m+ ~
d) S8 `' h0 P7 D; S5 f) l _wanted dates expl date compadd $* - $dates
# X! i3 a$ V. N9 Y; i$ a6 |( f5 H}
" k1 a+ g+ u# } z/ W! ]
9 j$ R/ j6 o9 Igenerate_date() {, w( _$ o% T' J: d
date -v -3d +"%Y-%m-%d"
! y$ O p( Q8 f2 I) D7 b! h date -v -2d +"%Y-%m-%d"
! E* U! n; z0 x5 t: m date -v -1d +"%Y-%m-%d"* x# v) o0 p. Q7 S8 H* x+ t+ q
date +"%Y-%m-%d"
; p! |! l1 W0 I3 W! [. x- X date -v +1d +"%Y-%m-%d"7 N* d* M. H9 |# G. s4 p5 U
date -v +2d +"%Y-%m-%d"& r1 T9 k7 W% _6 w4 s
date -v +3d +"%Y-%m-%d"% C2 F( S5 A3 e
}
! a; q$ w2 v& D" M7 t2 [1 o0 @# w8 {: S# G1 \' _# d( r
_cli_test "$@": Y: W5 B# v+ p, B/ E: a3 I
测试动态补全的效果3 A1 ]' R2 O8 n! W o
. |5 H' ]1 S) o$ E2 l' w$ D7 e9 A$ ./cli-test.sh -u -d 2016-10-<TAB><TAB>
9 I9 {: Y# B6 ]& t2016-10-14 2016-10-15 2016-10-16 2016-10-17 2016-10-18 2016-10-19 2016-10-20. C' `3 a( a, w7 f
R3 D5 n. T4 X' a" G" b( i
- A! h6 J1 B3 M% V9 r总结
! O* B. G8 h z2中shell环境下的自动补全都介绍完了,它们自动补全的机制都不难,只是 zsh 毕竟是新一点的shell,补全方式更加简单易懂。 特别是对于存在子命令和复杂的参数补全,以及参数内容动态补全的情况下,zsh 的机制更加易于维护。9 _' Q7 `& f0 |' V4 }
|
|