|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
. H/ ~9 M% c0 H4 j6 M概述
& l) c- o. \) Pbash 自动补全* t% U" \" V/ u! D. K4 Y$ @
- 测试补全的脚本
- 参数自动补全
- 自定义补全( D; T! {/ r, ^1 @% N
zsh 自动补全
& P; o; Z- f, v* |$ w- 参数自动补全
- 自定义补全
. n8 f! J% T- l7 f) F, J0 i% Y 总结 G' k0 _% T6 ?. C" |7 C
) o* ^7 e& t! l8 N0 K4 L% O0 u& }
概述6 A: D1 B: Y* M( I5 ~; |
虽然CLI(命令行)类型的工具由于其高效,易定制的特性为很多人所喜爱(也包括我自己), 但是,相对于GUI工具,CLI工具给人的直观感觉就是不容易使用,如果看到工具中大量的参数说明后,更让人望而却步。
* Y _) A0 W- e
/ t2 [0 q1 }: t因此,如果在自己命令行工具中加入 自动补全 的功能,就可以极大的提高工具的易用性,还可以保留命令行工具原有的高效。 这里所说的 自动补全 不仅仅是补全那些固定的参数(这些意义不大),更多的是补全动态的内容。
& e5 k- C- _1 q8 \6 m- N+ E3 t( {6 R- a7 }7 F
本篇主要介绍两种主流的 shell(bash 和 zsh)中,如何实现命令行工具的补全。
8 S0 A. S, x$ U$ N" _# z$ j$ F0 }! ]4 O1 U6 r" `/ ~
bash 自动补全
- M7 J* n. B- c; e测试补全的脚本
! j+ e8 h8 t$ A6 n' q简单编写一个测试脚本用来测试后面的自动补全:
/ L% _) y1 u- ?: z6 `; v" w& Q! J ?' H( C
#!/bin/bash4 T7 M, F2 P( H' K
# filename: cli-test.sh
0 _4 a& v& c/ h2 N0 b2 P5 W
* ?8 a% d/ S, t3 v" PUPCASE=false
* f6 H( T+ O5 S. e5 h% rDATE=""
& Y& T; ?% v; [7 Q6 x& _
* ]& M T* [( \8 C( x. \7 Z. Gusage() {
$ R. U; }- J& O echo "USAGE:"* z6 B: R' m' m& E, I& \' Y9 F
echo "cli-test <options>"
+ C: h/ w/ N' } echo " -h : print help"/ r+ \. U2 G3 V, L/ u9 e! Y0 N
echo " -u : print info upcase"- _ b& o5 K) a% \/ X' ~
echo " -p <xxx>: print info"8 ^0 O7 \: X7 X4 j5 r
echo " -d <xxx>: date in print info"
" @" S' t1 g1 ]8 [1 m* x/ Z" d8 t}
' _5 y. g+ @+ W' j2 q! h9 e; Q9 i* O7 s+ e4 L0 d4 Z
print() {2 v4 Q3 @$ G* U& p- T
if $UPCASE- x7 x& O+ G) r T$ {& R$ W
then
Q" |( E. e, g8 b2 o' ` echo -n $1 | tr a-z A-Z
' p( |% @& }% q+ I* \ else
7 @. q3 W4 z! g echo -n $1
6 ~/ J% O/ Q; I3 a# p3 t0 ? fi
- ~# T$ ~6 K8 J q+ ^+ j/ c9 o; ^( Y4 V( M- Y
if [ "$DATE" != "" ]
* ], J- V( Y6 e9 ^* x/ v7 C" N then
9 v, j) n! o# m! v echo " date: $DATE" O: c) @: z2 D
else7 z% [0 @ b7 D7 A1 [/ ^1 D$ {
echo ""5 V% Y6 \' T( g! ]8 d
fi: w; X$ X- @, \+ C- ]6 o7 k" b, X
}
* w) a$ n* }* w/ c- L
6 `9 g) l- C! p, G6 p! i1 Jwhile getopts "hup:d:" opt; do
7 a, Y/ K: M/ s3 u* e, M# U case "$opt" in' f7 h7 A% W+ i# c! ^; ^
h)6 | h9 q0 a( U7 p# M3 P
usage& y+ R. W" s6 w
exit 0
: ~3 N2 \# i0 Q, B5 ~8 [ U* P ;;
( T( U1 D& F! J( Y; D' q u)
, [$ s; v5 Z& g, ~ UPCASE=true
6 I& \4 P- D% P ;;
4 Q5 `, [% N) {' r" K d), @ r. e, q& B) g7 o
DATE=$OPTARG% N0 c! |* ]- e5 @
;;
0 ?; ~2 ], a4 g; Q4 ]5 I+ ? p)
# t6 ~/ V8 |8 a$ c, L) V1 y print $OPTARG; [* t% \/ N1 T7 `6 o2 d7 A3 F, Z
;;( A, p8 H/ b/ F8 N( M* W+ u4 Z
esac
6 z( U9 c0 r" O1 _: U6 d2 f adone
& c$ _2 S5 r0 S* h测试上面的脚本如下:" n* t1 t! \: C/ L9 [& `3 F x
$ w4 U* n2 p0 |* H4 gbash-3.2$ chmod +x cli-test.sh+ Z. b* B3 t; k R; o+ E
bash-3.2$ ./cli-test.sh -h7 K* b9 {. n; f6 G
USAGE:" v: Z; B( S9 w) l
cli-test <options>
: Q$ q' l6 V, l -h : print help8 v4 M, J# {9 k8 A* g% O) S5 h; A
-u : print info upcase
6 r9 d+ O& q2 B5 B X' n -p <xxx>: print info! D* t1 ?, z( y Y% A
-d <xxx>: date in print info; q2 ?5 W, T$ P. U, T: {7 v
bash-3.2$ ./cli-test.sh -p hello+ @" q" _" R# k6 J/ I `5 g! i9 D
hellobash-3.2$ ./cli-test.sh -p hello6 `( C; X0 Y" O# Z3 w
hello
1 W) o W* O0 K- v pbash-3.2$ ./cli-test.sh -u -p hello
6 ^2 B6 @4 i% ]! @6 m# UHELLO
9 C0 ?5 K6 d/ B* Z$ obash-3.2$ ./cli-test.sh -u -d 2016-10-13 -p hello# v. }4 V9 J8 ^# G# ]( x8 C
HELLO date: 2016-10-13) ^4 o/ l, r, [8 A% \4 @; l! q
- {- j' ~" T3 |* M. I6 J* O
& e3 I. Y6 i- A% Z参数自动补全8 T5 Z+ y1 D# \* r
参数的补全一般来说比较简单,因为一个命令行工具的参数一般都是固定的。 下面的参数补全脚本是针对 上面的 测试补全的脚本 cli-test.sh6 [, W% w% J& O" V
4 M1 _6 F' h& O. t& H_complete_func() {
& W% l% U) H' Y3 E/ H C* { local cur prev opts base9 b* M7 x7 e$ _/ J6 Q" g. m
COMPREPLY=()
! A: {& F$ K5 U0 V cur="${COMP_WORDS[COMP_CWORD]}"
% x2 ^( e# a6 i/ z8 R6 v* D prev="${COMP_WORDS[COMP_CWORD-1]}"
8 X1 \/ Q& v& c5 e- T2 Y1 D2 ]2 M5 q, T
: g4 i) v, D9 h" G) m$ U" n" w" ^ opts="-h -u -d -p"; S- ~* ^$ B/ y3 V) [* \3 X( R
8 F) Z2 {( c3 Y' v& \) n- J if [[ ${cur} == -* ]] ; then( q# w* `5 u: o) {
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )$ V- M5 ~6 L1 f9 a+ \; q
return 05 M' \$ B ]' T( S; v
fi4 t$ f0 p c. i7 N- r( ~7 J
; E: Z1 \2 B; @9 M$ ]; m7 X}% G0 s$ O# K( |- g1 S
/ g4 e Y7 t$ u& A
complete -F _complete_func cli-test.sh
' a9 S3 h% S% k) e' R C+ U) C让自动补全脚本生效的方法如下:
2 T( K5 S( I0 P4 N' ], R Y/ \/ U4 N# D: p
bash-3.2$ source bash_complete # 使自动补全脚本生效
- N v) ^' g4 h" _. f5 P9 B) M! Q4 Kbash-3.2$ ./cli-test.sh -<TAB><TAB> # 这里输入 - 之后,再输入2次<TAB>就可以把所有能补全的参数列出来* I7 Y( L* m m; q: \; R! ~
3 q! o( _: k. }, a; m6 {
: N; G, a4 l# x8 Z; K, v: ^4 P5 S4 |( p6 I
自定义补全
2 X. ?4 l7 \* D7 U+ V/ S: y上面的补全是补全固定的参数,简单,但是用处也不大,用户记不住的其实就是那些会变的参数内容。 下面尝试动态补全 cli-test.sh 的参数 -d 的内容(内容是当前日期以及前3天和后三天的日期) 修改 bash_complete 脚本如下:
+ q+ i1 K# _1 B4 O, R$ Y7 y1 ?% M3 B$ A( u1 L' j; w' ]
_complete_func() {
* K1 E0 f; Q! h \) | local cur prev opts base/ D$ }$ g9 D- U
COMPREPLY=()+ [% U* Z1 U) E, H3 R- \
cur="${COMP_WORDS[COMP_CWORD]}"
t, r2 [/ A6 T% A: [! c prev="${COMP_WORDS[COMP_CWORD-1]}"* W6 K# ]; |/ @% Z/ G6 ]6 M% F
, k6 m# |* i7 ?- D$ x T t! ` if [[ ${cur} == -* ]] ; then
. d W$ L* k( p opts="-h -u -d -p"6 |. x4 W6 a9 q" ?$ @
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )6 P! S3 L7 V6 E4 F: U
else! s0 x6 e2 z, `1 A1 f* B: f) U
opts=$( _complete_d_option )
5 B& f: e1 V! N7 h) u+ V7 O COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
- B4 `4 m' i' g4 | o fi
6 M% q( @; v/ E- D
5 Y' Y( ?% v/ K6 n* o3 o# P return 0+ B2 S# [1 a5 P) g$ }6 l G r! E
}& M' S! ?0 w/ E* b: Z
0 k) b* B& }3 N_complete_d_option() {
+ L' y; C. m) U# ^ date -v -3d +"%Y-%m-%d"
) e3 P8 D( M# U* E3 ~ date -v -2d +"%Y-%m-%d"2 f2 B; Y3 ~! n" ]$ k6 {
date -v -1d +"%Y-%m-%d"
5 ~/ g9 \, t- l3 X- ?: ]3 j date +"%Y-%m-%d"
# d b T$ T/ t date -v +1d +"%Y-%m-%d"
- }* l% i# [& a6 t5 z& [ date -v +2d +"%Y-%m-%d"
, R& s8 @7 t4 E3 q. e# ?5 a0 Z date -v +3d +"%Y-%m-%d"
( O3 W$ W5 O, \( B1 U% _" M6 ?}
/ y" n1 a4 N) k3 q( h3 c* a. n, q z& ^! t/ K$ x6 Q3 n
complete -F _complete_func cli-test.sh! ]3 ]. b' {! Z2 I8 t
测试动态补全的效果8 H" Z# Q8 x1 x- c* s
V* |$ S9 ~& V, wbash-3.2$ source bash_complete # 使自动补全脚本生效
9 [7 y) _6 l! o7 `' j$ E- `bash-3.2$ ./cli-test.sh -u -d 2016-10-1<TAB><TAB> # 这是 2016-10-13 执行的结果,其他日子的结果会不一样
Z+ x t3 b+ i1 ]% E2016-10-10 2016-10-11 2016-10-12 2016-10-13 2016-10-14 2016-10-15 2016-10-16
* o3 A8 m$ U9 e5 `上面就是动态补全,_complete_d_option 函数就是用来实现动态补全的。7 |3 \# D: T0 W& @$ C
; @/ j: y) q; R/ w( O
1 J$ _, _, G% l, b4 ]zsh 自动补全
$ M( m; ~9 j4 `, N9 R相比于bash,zsh 的补全机制更加强大,也更加直观。 同样,下面也通过例子来演示如何在 zsh 中实现上面 bash 中同样的补全功能。4 v; U4 \' a- D9 C1 b3 f
8 [1 J9 T$ X, L+ K* x2 H1 Q/ v4 T1 U& R2 ]) F8 g/ o# {
参数自动补全! w8 y9 b& X- N* B# C$ u
相比于 bash 的自动补全脚本,我觉得 zsh 的补全方式更加直观。
d- I# z7 @4 w! v0 Z4 ?( Y- o; A6 \7 u$ d8 V6 N/ z3 H9 h! t3 [
#compdef cli-test.sh
" T( R0 h% S: L$ `% U6 X# k" l# filename: _cli-test.h
0 r" n! j5 [, @+ @5 l' o8 V, f# R
' L' a6 S4 S! H8 n_cli_test() {
8 v- d/ g- x1 p: @# y# ?
0 B8 k9 B3 c7 P% l- q _arguments -C -s -S \
3 i2 N9 T9 B- K) [8 r9 p. L; i '-h::' \8 Z- E: w' W$ H6 U: d6 k
'-u::' \7 ^6 _ ?( P# S4 W
'-d::' \
9 _" w) r- _* n& C d5 R4 E '-p::'
& H [9 M: ^6 n5 o& r, T}" r& D' i% o) k' f
' C$ ?5 M- |, z L3 m_cli_test "$@"3 b& O. J: X7 q( v7 o! J' I" W: K
zsh 中有个 fpath 的内置变量,将自动补全脚本放在 $fpath 中,或者在 $fpath 中创建指向自动补全的脚本的软连接都可以。 下面是我的环境中 fpath 的值& A4 w5 L( k* A8 }
1 u. f' D2 e% u* g8 [" T- C
$ echo $fpath
% e* r- M. E& b) f1 P, H$ \$ }/usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/5.0.8/functions$ x9 |9 A& K' N; ` e3 B8 s
为了测试 zsh 下自动补全是否有效,我在 fpath 下给自己的自动补全脚本创建了软连接
9 L9 j& @" p6 j& X# `: \ K; ^
0 \( \, s6 w1 D, ^$ cd /usr/local/share/zsh/site-functions6 Q' Y m, _* z0 W2 T9 x
$ ln -s ~/projects/bash/autocomp/_cli-test.sh _cli-test.sh
4 ~3 R; N: B6 a( D d7 N5 D测试结果( R; D' ]' F8 Y, B( H- @
' y3 \% C7 ^- S& X; W& h
$ ./cli-test.sh -<TAB><TAB> v; ~$ E! s$ f/ f6 b* u: e
-d -h -p -u
& @8 s# Q5 A$ b. L; l可以看出,zsh 的补全方法非常简单直观。稍微解释下上面的代码& P1 _+ U- L& n
# _! u/ @3 [( S4 R1 k$ {_arguments
0 W( L3 H6 p* A6 u, B这个函数是 zsh 自带的,有点类似 bash 中的 compgen ,但是功能更加强大。
' W. [: p7 L2 w& Q% A% d
% ], ~# K5 e5 `'-h::' \* j* l6 i- v$ z+ j
这里 : 分割的3部分分别是 “待补全的参数:参数的说明:动态补全参数的内容“
" e2 o# G! W/ C. k% j5 \6 Q) j4 i: k1 A# L# S
1 c$ j- J1 K9 L自定义补全
8 e S# t3 y% ^, V( x3 p根据上面的解释,要想动态补全 -d 参数非常简单,只要加个函数,并配置在 -d:: 之后即可% W8 E7 \3 O p" T) h5 u( u# V
) h5 k4 b; w; |! L8 D y/ @0 G
#compdef cli-test.sh9 Y1 K9 s$ e) B* n
# filename: _cli-test.h5 t/ b3 x' E6 r) J8 G2 V
5 y, B2 s+ ~4 u4 j/ I5 z1 Q
_cli_test() {
# w) J/ |7 b# Y5 K) i7 j
; N( F1 Y( m! Y( F) P _arguments -C -s -S \; g L# s- U$ A2 Y8 h# |
'-h::' \ ]* R6 \/ }/ w. {; D
'-u::' \# J) u/ P! U9 ~" |/ ]( g
'-d:auto complete date:__complete_d_option' \" x7 l3 I( z2 U; V6 k: y
'-p::'" Y7 g: _+ t) b
}
+ O# N/ z7 s2 x3 `9 `% I. g) X+ E' d, p8 [4 E; [
__complete_d_option() {
. E8 c% `9 } a( }& ~ local expl3 |* r! q7 {' s \6 s
dates=( `generate_date` )
: {( v0 s6 B3 Q7 k% o
- n* D! K" ?, u4 C _wanted dates expl date compadd $* - $dates
" i+ ?! q" i$ j! `# L1 Y. V}
! J u4 t( N. n n K3 H* T+ t
/ i( O( T5 |/ T! D' A! h' b; Ugenerate_date() {7 A$ i3 k9 w7 f% z
date -v -3d +"%Y-%m-%d"+ \( n s* b8 I) V" M; E% m& B
date -v -2d +"%Y-%m-%d"
9 b8 E4 j6 b0 X date -v -1d +"%Y-%m-%d". V" Y" q) E" I$ E# Y
date +"%Y-%m-%d"
$ E4 R; d) X. i, x- W4 w5 ~8 Z date -v +1d +"%Y-%m-%d"6 y) G9 W9 i5 P
date -v +2d +"%Y-%m-%d"
0 i% \& O7 q8 ~2 S* w0 c7 e date -v +3d +"%Y-%m-%d". v* Z5 }/ e0 q+ m9 F
}
+ S3 I$ v! S* f$ H" H; A" k$ S: f$ ?" s, `/ Y
_cli_test "$@"/ F' O" R# ~- g
测试动态补全的效果
$ u. ~ F+ M) o: l2 ?# r. y, A* V; Z s; n& E
$ ./cli-test.sh -u -d 2016-10-<TAB><TAB>0 J' x4 J7 A, A g3 T0 b- |+ t
2016-10-14 2016-10-15 2016-10-16 2016-10-17 2016-10-18 2016-10-19 2016-10-20
6 H7 L2 s: f" ]# @) u7 q8 g2 q" A1 k$ e0 V2 y( G
& }: ]9 K! w I7 `% A' d0 s8 ~& u总结
+ n6 u9 N5 ^" A0 ~+ x% x2中shell环境下的自动补全都介绍完了,它们自动补全的机制都不难,只是 zsh 毕竟是新一点的shell,补全方式更加简单易懂。 特别是对于存在子命令和复杂的参数补全,以及参数内容动态补全的情况下,zsh 的机制更加易于维护。7 A/ @3 y! |% G( Y
|
|