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

命令行自动补全原理

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
' n* m( ^) s4 A! ^0 Y
概述( h9 I2 ]+ M9 F2 q/ O, P% _1 v4 E
bash 自动补全
6 S% T: L$ K4 ?+ X
  • 测试补全的脚本
  • 参数自动补全
  • 自定义补全
    - p/ I$ ^" I+ U/ K1 Q
zsh 自动补全
9 C$ |4 J5 J; ]. W. |% A
  • 参数自动补全
  • 自定义补全
    4 P0 c" b7 c2 I
总结
' q6 H) Z% W+ v3 }" n$ @8 M# Y- p+ K  i
概述
: X( D$ ~  p9 d; Z3 F0 j4 }虽然CLI(命令行)类型的工具由于其高效,易定制的特性为很多人所喜爱(也包括我自己), 但是,相对于GUI工具,CLI工具给人的直观感觉就是不容易使用,如果看到工具中大量的参数说明后,更让人望而却步。
; |' Q/ R- }+ q3 c  J2 |( _; V% u/ R$ N9 c* H
因此,如果在自己命令行工具中加入 自动补全 的功能,就可以极大的提高工具的易用性,还可以保留命令行工具原有的高效。 这里所说的 自动补全 不仅仅是补全那些固定的参数(这些意义不大),更多的是补全动态的内容。
) D' u, Q) Y5 j0 h
' F1 V# i& b) ^9 i3 v7 I; D本篇主要介绍两种主流的 shell(bash 和 zsh)中,如何实现命令行工具的补全。$ N2 t' v& l# P! r* N* w

7 X8 k9 `" Q. e  u" Tbash 自动补全% i9 T' m2 Z3 K
测试补全的脚本
' F$ \6 A% Y5 [+ ~& d; r# z简单编写一个测试脚本用来测试后面的自动补全:
5 J& J7 X' g  R* e# A
2 Y6 X! o% h  ^+ K#!/bin/bash. D7 P* K! S- y. x; J1 Q
# filename: cli-test.sh
6 \) @" K( N4 b" s( U4 i6 }! z9 Y- Z3 n' _9 L5 C: g
UPCASE=false
8 B+ M, u. U5 H# {* R  |, nDATE=""
2 N* Q0 j# K* d) E- Z8 b5 i6 U9 |" I- k; p
usage() {
/ j6 |: @3 m9 s: d- J5 d    echo "USAGE:"
* o" ^$ W. d- ^0 f* ~1 ^    echo "cli-test <options>"# H/ v6 l4 n) x/ E+ b5 m
    echo "    -h      : print help"$ O$ y+ G* {( t
    echo "    -u      : print info upcase"8 B& N& ^8 D1 e- J
    echo "    -p <xxx>: print info"
) {+ ]6 Y5 K  I    echo "    -d <xxx>: date in print info"
4 K% g' T( {: u3 Y2 M}3 n3 H) W: @. q
# ]' r. [5 O1 V# m
print() {
/ Y2 _% J& C- _9 ]2 @- W5 ?1 P    if $UPCASE) x' b7 m  E+ @" k4 @
    then5 A+ @, x: h& }/ K
       echo -n $1 | tr a-z A-Z
3 X- V* A; T% r" G  P    else
% p1 Z. o" r/ b1 }        echo -n $1
! B% a7 A% s" G0 o) `# M    fi8 r2 A! S- |% H9 b! T1 {: e. B
9 `. P0 O  l1 s1 I. V( h2 S  e
    if [ "$DATE" != "" ]( l6 j  }; H, C
    then
, G6 D7 p, Y( S2 i! M. ?        echo "   date: $DATE"  q, Y- |7 O2 u2 v' r# y
    else
; _' m$ O& V7 _& I        echo ""
4 O& t9 Y! f( G- r$ V$ Q- s4 v: g' m    fi
  u/ `* A1 ?8 }/ d4 |1 z}' A, t: `! d2 W3 h% O

. r$ x# Z/ }+ g6 U6 F0 @# Lwhile getopts "hup:d:" opt; do* g5 x/ @0 V* f2 e% r
    case "$opt" in
$ L6 y! e3 w* n' `8 u        h)# Q) W. Q8 P- V9 D* O( r
            usage
; u) Y' ~: X6 z# O& p% y1 g            exit 0+ B0 F. }, G/ H) U% d) {
            ;;+ R( E. P" u6 c7 r& E8 p7 c; O
        u)+ q& Q0 E5 ~+ t0 [' v( s! @3 M1 l  X# \
            UPCASE=true% R. k* ^+ Z5 C1 I0 S
            ;;0 o8 b& M: y. Q  Y# r, l# k4 v
        d)
% m8 v  x6 |; s3 b2 ~            DATE=$OPTARG
( o5 \# C/ Z( R' T$ g  \: O            ;;3 {. \6 U, q+ U1 Q
        p)0 j! E$ d0 N; m1 [
            print $OPTARG' |8 a" h4 V' t* H
            ;;% r& H1 ]2 |. Z$ {( M7 ~7 r5 _
    esac
) ~1 Z" d% q8 x) E' U- O( r& Ddone
; h3 A4 G5 ?! v. T- g; \测试上面的脚本如下:5 t; s9 V/ r% _0 R! m- J8 _; ]8 {
$ Z3 K' J: y; {2 q$ |
bash-3.2$ chmod +x cli-test.sh
/ ]& U" K* q5 E0 W* R( M; bbash-3.2$ ./cli-test.sh -h3 |% R" k1 u  g9 a( m
USAGE:
/ w' u) J4 Q) j6 b+ Ycli-test <options>9 \" n0 D1 O" @, L( p4 @' B; |
    -h      : print help! {) }. x1 c* [/ a* L
    -u      : print info upcase
7 ?+ T7 G: y, X2 V    -p <xxx>: print info+ s3 |2 P8 p+ Q* [" A; z
    -d <xxx>: date in print info, \, T' x+ d( a+ R% A: D- O
bash-3.2$ ./cli-test.sh -p hello( M5 d4 A' U+ d1 X% j6 J
hellobash-3.2$ ./cli-test.sh -p hello5 ^( R: K0 o# S/ ^2 r" f
hello8 c& t& k& [( ]& u! Q8 P6 O2 b
bash-3.2$ ./cli-test.sh -u -p hello) \; M/ C& d* R, \  R
HELLO3 |8 x8 e. }* H' o
bash-3.2$ ./cli-test.sh -u -d 2016-10-13 -p hello
/ q# y; G+ d: o% @HELLO   date: 2016-10-13' |! O7 Q3 X# b; E. K
9 @- s+ c1 h1 @) n! H7 {8 r

4 f$ k" L8 k; }* x9 `参数自动补全, b: f. o' Q( G: h' y4 F- Q
参数的补全一般来说比较简单,因为一个命令行工具的参数一般都是固定的。 下面的参数补全脚本是针对 上面的 测试补全的脚本 cli-test.sh" R6 Z5 q: t' ?: g! H2 o& f
& F7 z0 n1 f6 h: g7 }
_complete_func() {
5 g- z0 A  b7 }6 |: D/ S/ T    local cur prev opts base
8 a7 q/ T9 ?  n. O/ h8 k    COMPREPLY=()% ]/ U+ A) t! \8 y, \! |# A
    cur="${COMP_WORDS[COMP_CWORD]}"4 U2 A; p+ h5 p6 V
    prev="${COMP_WORDS[COMP_CWORD-1]}"7 v) T1 N& ]0 J6 g, j

5 p0 X6 M- }. a2 Q    opts="-h -u -d -p"
* b% y  r7 j4 z
  a( o2 N9 N+ @1 p7 Q& E0 j4 ^    if [[ ${cur} == -* ]] ; then
. t9 W% N3 [  N: R        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )9 }( R% K1 `" X  W0 q! b; G8 M0 t
        return 0$ _, K2 a2 S8 Z# w/ t4 g9 H
    fi
/ |+ ^3 ]; p! v& o
% p3 T3 t) ^; a}
1 D2 z) R8 }- }1 D' Q/ q/ b! t& q, O- s/ L3 ^) s' h
complete -F _complete_func cli-test.sh* T. K  Y: g4 h/ u7 e- V9 r* w
让自动补全脚本生效的方法如下:
3 i( g+ b& `) a7 v+ R9 |* i5 r' w, V
bash-3.2$ source bash_complete          # 使自动补全脚本生效
$ y. J3 F  r- ?bash-3.2$ ./cli-test.sh -<TAB><TAB>     # 这里输入 - 之后,再输入2次<TAB>就可以把所有能补全的参数列出来
& Z0 Z2 e$ P; K+ a9 c/ G' t/ E& A) A/ e; |( y$ _1 D+ |) _- G
. w7 ?  n/ t& o' a2 C% ]. a" [
7 c9 ^! L' c9 d7 g
自定义补全2 c0 y2 b$ B+ h' q" k" d/ `
上面的补全是补全固定的参数,简单,但是用处也不大,用户记不住的其实就是那些会变的参数内容。 下面尝试动态补全 cli-test.sh 的参数 -d 的内容(内容是当前日期以及前3天和后三天的日期) 修改 bash_complete 脚本如下:" w0 e4 R+ }& B
: D! O5 W' ?- Z# [% R
_complete_func() {- t: K' L4 B( X
    local cur prev opts base
3 m, n% l) b$ d$ C: i* Q  W0 J    COMPREPLY=()5 z+ }" p) M: y. T, v0 M
    cur="${COMP_WORDS[COMP_CWORD]}"
! b2 N- e( _0 y- E+ i/ I    prev="${COMP_WORDS[COMP_CWORD-1]}"( a3 I2 c' x+ J

4 H( j" d7 ~7 p( m; J% h/ E" j# M    if [[ ${cur} == -* ]] ; then" U( |) v! j7 l1 W/ z
        opts="-h -u -d -p"8 P+ I5 V  o& H. p
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
4 H# t) N7 Z* A) Z% Q; a, n6 \    else7 o9 n3 ]! d5 K8 p" _# t
        opts=$( _complete_d_option )
$ w/ s$ n; a2 P3 S6 ?+ u        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
/ F$ i+ v+ _1 Q$ X! l    fi
4 O* |" x# V% S% |6 H4 s5 b
" ?$ |1 `+ q% }& `, ^6 l: f    return 0
/ p; G4 m- {3 K5 C0 d; l}  b( l7 k3 N; z/ c. u: ?8 c0 Y

3 h2 r0 ^# z+ T- |7 e3 l* W6 L_complete_d_option() {
  ?, _6 d  d  J( u- T1 H    date -v -3d +"%Y-%m-%d"
; ]& ?/ L+ g/ C, R    date -v -2d +"%Y-%m-%d"7 {& \* w* M/ ]( @
    date -v -1d +"%Y-%m-%d"
, o" e. i! e' ~$ b4 h7 y0 }$ J    date +"%Y-%m-%d"& f+ E! A9 a7 n/ d7 B# ~, I0 I
    date -v +1d +"%Y-%m-%d"
+ {$ v/ d- W# T( g" M. E    date -v +2d +"%Y-%m-%d"/ n1 g* u* J0 j* J1 Z
    date -v +3d +"%Y-%m-%d"
0 v8 T" p+ L* H; _6 y}) U# P( W6 w( y; x1 [% c! ?

7 G  r8 o6 I, ?2 O+ t* hcomplete -F _complete_func cli-test.sh
, Y& G7 I/ g, i# H% a测试动态补全的效果" m8 c* w& m# J. m& {
( Q$ a+ d1 U! [
bash-3.2$ source bash_complete          # 使自动补全脚本生效
' x: \! f. A( C" h: A  C( }+ ^' Xbash-3.2$ ./cli-test.sh -u -d 2016-10-1<TAB><TAB>   # 这是 2016-10-13 执行的结果,其他日子的结果会不一样( K; h+ X. s" `- e  w- O' D+ g; x1 o
2016-10-10  2016-10-11  2016-10-12  2016-10-13  2016-10-14  2016-10-15  2016-10-16/ v7 [( S: p) V7 N/ c7 H
上面就是动态补全,_complete_d_option 函数就是用来实现动态补全的。) s6 Y/ H& i% W+ k; E- [/ N0 H
6 ]2 d+ ?5 T- U6 F

3 ^" m5 |$ \& I( E, [( A6 z8 }zsh 自动补全) c1 W* D6 [6 O+ e4 u# k& F
相比于bash,zsh 的补全机制更加强大,也更加直观。 同样,下面也通过例子来演示如何在 zsh 中实现上面 bash 中同样的补全功能。
# {+ N3 e. S3 F# @: b) r( \% K0 B4 u9 C& u# u

, L6 z9 Q. c% g7 ]7 j% O0 b" L7 o参数自动补全4 U0 r3 n  j* j8 a
相比于 bash 的自动补全脚本,我觉得 zsh 的补全方式更加直观。- V9 A% k. v% n7 K2 f) E
% J8 Z( t, i/ N3 ^, X8 [
#compdef cli-test.sh
8 a- Y, H" U/ ^' y# filename: _cli-test.h
, j% ?/ i+ W. f7 @  [: F: N5 G6 o; F$ I: w! [
_cli_test() {
3 d, r# l% F0 P! Y( C! \) Z5 B
% T) t4 k. j+ Q# H; T+ ?  _9 K- h    _arguments -C -s -S \$ k2 y' i$ x- q
               '-h::' \# e3 e3 Z3 o, T( K& E! F3 t+ m
               '-u::' \, t8 _4 M4 j% ]5 @; o" e3 e
               '-d::' \$ `/ A; g; l0 f; ^' @3 G
               '-p::'
: |7 ?# s- f' u}' S' Y4 z& P* m" R1 ~% p. G1 W( \
  T6 k% {; U( e2 m* {6 J
_cli_test "$@"5 q  r+ X+ R* ]5 J
zsh 中有个 fpath 的内置变量,将自动补全脚本放在 $fpath 中,或者在 $fpath 中创建指向自动补全的脚本的软连接都可以。 下面是我的环境中 fpath 的值
8 V- H6 Y. e& R5 s' X+ ]) q: n: C; z# S8 ~, \
$ echo $fpath
' F/ T6 A+ {* E5 L0 {/usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/5.0.8/functions2 M( h8 a  }8 u! V
为了测试 zsh 下自动补全是否有效,我在 fpath 下给自己的自动补全脚本创建了软连接7 L; H+ H  q& K, Y

, K1 G. ]' P2 S2 u" m8 `$ cd /usr/local/share/zsh/site-functions7 @$ {/ R/ Q3 m7 I2 z
$ ln -s ~/projects/bash/autocomp/_cli-test.sh _cli-test.sh. o+ N. [. {  e, M* t
测试结果% }) |4 O7 n0 N+ S. k9 r$ H+ w5 M/ Q
* E. L( s' k7 S+ t5 s' d
$ ./cli-test.sh -<TAB><TAB>* u+ V! y0 F# a" p' L
-d  -h  -p  -u
1 E. K8 R& l5 q! d# j' a可以看出,zsh 的补全方法非常简单直观。稍微解释下上面的代码- e9 C& w3 K/ a7 p

2 t4 s' M& T9 L/ y+ H_arguments8 q  h9 R" I+ p* o, [/ Z5 U
这个函数是 zsh 自带的,有点类似 bash 中的 compgen ,但是功能更加强大。
, n3 h: p! ]/ f- B. n- w$ X4 B* Q0 e7 j0 ]/ ^
'-h::' \
: S7 g1 A/ J" \9 [7 y$ E这里 : 分割的3部分分别是 “待补全的参数:参数的说明:动态补全参数的内容“/ t% B7 E, Y; I8 ]  ?( x

1 Y6 X! U2 J5 n( q2 J" ~7 k" A9 X. C! L! K
自定义补全/ B, m4 P% j% K* |8 o
根据上面的解释,要想动态补全 -d 参数非常简单,只要加个函数,并配置在 -d:: 之后即可( F  S2 `0 O  x. d
  f# i- y( C/ t; L7 b3 a
#compdef cli-test.sh
4 @& R3 [; E1 ~$ E% M4 k$ u# filename: _cli-test.h
+ k% {! b$ l" ]! l, F# L# G3 j) k' }! M- }8 v4 \/ i7 |
_cli_test() {5 J, T. [! R0 k) P$ `# U; a8 G, E2 g

! Q1 w; U& v# T4 F( G' q) Q2 a- P    _arguments -C -s -S \8 d' e9 D5 |# K' z% C
               '-h::' \8 K1 B3 K8 p, `% u# V& ?
               '-u::' \/ n* K3 C7 O% J5 o
               '-d:auto complete date:__complete_d_option' \
" u/ a/ n+ V& U" \. a% z: t  ~               '-p::'$ W: K! o2 s; D# @( D
}
6 D# y% _1 ~5 \: h: _9 u# I: J8 P' P% y, Y. {0 D4 G- I
__complete_d_option() {
: P7 f' _6 K( g5 \; T) C    local expl
  E6 j; _  G# C    dates=( `generate_date` )
) w: E2 G1 E5 g: B* h
% k) @6 v5 r! g6 i4 |- W    _wanted dates expl date compadd $* - $dates, ^* M$ c  p  S% L/ _
}7 \3 O$ @) a+ }! H$ [$ T5 h* p, l/ w) Y
! y3 B3 S( i# n; s/ W/ X
generate_date() {
& N( G5 H& Q% y, h8 e1 x    date -v -3d +"%Y-%m-%d"
8 u7 H. \: ~! ^% |% B* F, M: W    date -v -2d +"%Y-%m-%d"
) `3 M  ], p6 D5 {' f) n    date -v -1d +"%Y-%m-%d"' z) f, J5 |% G$ O& O0 _2 n4 H3 k
    date +"%Y-%m-%d"- r6 w7 V! z1 h& ?# ^) l7 Z* v3 ~1 h
    date -v +1d +"%Y-%m-%d"! B! g% T2 O7 w* b% O# m+ w( m+ d
    date -v +2d +"%Y-%m-%d"
, e# n' G1 B% S6 f    date -v +3d +"%Y-%m-%d"
: D" I) g) @4 a* p3 I3 ?}
1 S* v$ v6 M2 c8 l1 g9 M! V
9 J& z" x0 Q8 o9 h, K2 R) s2 `7 g_cli_test "$@"
3 \6 I6 y# f% x; i  V+ [' U8 S! \0 F6 b测试动态补全的效果  U5 e  k3 q+ W% R

  r7 V" B, d1 b$ i$ ./cli-test.sh -u -d 2016-10-<TAB><TAB>: R+ i( V3 c, Z' u, A
2016-10-14  2016-10-15  2016-10-16  2016-10-17  2016-10-18  2016-10-19  2016-10-20
! P' d. i2 p$ ^' B3 l5 N4 ?9 o. z7 }7 w" T
% l4 q" ~8 ?7 `/ I% ~
总结9 R8 P" W4 L2 E9 _* K
2中shell环境下的自动补全都介绍完了,它们自动补全的机制都不难,只是 zsh 毕竟是新一点的shell,补全方式更加简单易懂。 特别是对于存在子命令和复杂的参数补全,以及参数内容动态补全的情况下,zsh 的机制更加易于维护。
# \% E# k  f' a  g" B- y9 Z
  • 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 16:37 , Processed in 0.156250 second(s), 23 queries , Gzip On.

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

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

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