找回密码
 注册
关于网站域名变更的通知
查看: 212|回复: 1
打印 上一主题 下一主题

命令行自动补全原理

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-7-6 17:20 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

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 }
  • TA的每日心情

    2019-11-29 15:37
  • 签到天数: 1 天

    [LV.1]初来乍到

    2#
    发表于 2020-7-6 18:52 | 只看该作者
    命令行自动补全原理
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

    推荐内容上一条 /1 下一条

    EDA365公众号

    关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

    GMT+8, 2025-6-29 02:10 , Processed in 0.078125 second(s), 23 queries , Gzip On.

    深圳市墨知创新科技有限公司

    地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

    快速回复 返回顶部 返回列表