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

命令行自动补全原理

[复制链接]

该用户从未签到

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

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

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

    [LV.1]初来乍到

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

    本版积分规则

    关闭

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

    EDA365公众号

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

    GMT+8, 2025-11-25 21:31 , Processed in 0.156250 second(s), 23 queries , Gzip On.

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

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

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