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

命令行自动补全原理

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x

! o4 O  ]" x0 s概述
9 |! I- W0 H) c4 g( B6 ^* Z' Lbash 自动补全
$ ~2 j/ _2 g) K' I9 r/ M5 D7 w
  • 测试补全的脚本
  • 参数自动补全
  • 自定义补全
    , D$ J+ U0 e7 u
zsh 自动补全! g7 p3 w  O/ h& C( ^( ~
  • 参数自动补全
  • 自定义补全6 A8 v$ m; ]1 G* R. ]
总结( O; H! T; {6 a2 _3 t! K

. V+ F3 k8 S. @; `# z) V$ ?概述
4 O4 h* b' D3 L& L8 _虽然CLI(命令行)类型的工具由于其高效,易定制的特性为很多人所喜爱(也包括我自己), 但是,相对于GUI工具,CLI工具给人的直观感觉就是不容易使用,如果看到工具中大量的参数说明后,更让人望而却步。* {5 Y3 Y' z3 |' f, ?
0 `: g! e) f2 C5 y2 A
因此,如果在自己命令行工具中加入 自动补全 的功能,就可以极大的提高工具的易用性,还可以保留命令行工具原有的高效。 这里所说的 自动补全 不仅仅是补全那些固定的参数(这些意义不大),更多的是补全动态的内容。- W5 P) X9 C; o, [: f$ H8 O

4 N: E6 G, h8 p% f7 m3 q3 O本篇主要介绍两种主流的 shell(bash 和 zsh)中,如何实现命令行工具的补全。
- k% w% r: H# o/ f7 [0 l* ^/ E/ q) Z/ `/ G( w
bash 自动补全0 A4 o" ?; o# p6 J" G
测试补全的脚本
5 I' x& d( Z. A) j0 Y9 \简单编写一个测试脚本用来测试后面的自动补全:
  R3 C9 ?4 a  N1 _3 Y, _
  S1 ?/ J6 {8 r#!/bin/bash
1 o" f$ C5 P' ?# filename: cli-test.sh4 d5 n: _) ?0 Q' ~
2 S% D% X' n: Z4 ^$ p, b
UPCASE=false
) i9 |- L+ T7 D4 t$ f! NDATE=""
% ^1 D) c, N2 x" x, s* j, i
( p6 ~! t  N' u' v) C# Qusage() {
- v+ r. W+ e6 x: p; q* X' G    echo "USAGE:"
' t$ ~& _5 l/ l  m' O/ F    echo "cli-test <options>"
" k1 s5 k2 b+ _+ p    echo "    -h      : print help"
! L4 U1 n+ Y0 o! ?* Y8 j    echo "    -u      : print info upcase"
( }+ D: [1 j- m7 ?2 h8 G3 G    echo "    -p <xxx>: print info"4 z+ F* P2 C1 O7 h5 ?' u3 `
    echo "    -d <xxx>: date in print info"
) A% D: \/ t) F7 r* D}
% M) Z2 s7 I' d: p0 C2 Z
1 w* N: V9 x, i* c2 n1 Lprint() {
1 q8 j8 M! X7 H: i. L3 n  w    if $UPCASE
& r5 c$ i2 W3 J$ Q; T. V    then
8 S2 I- |8 l$ o. {  x' q3 k: t       echo -n $1 | tr a-z A-Z+ F; }4 `! @: ~' u( s6 t1 Z" o
    else
5 o) \# q; V% Q. s4 U        echo -n $1( `- o6 n6 A6 h* q0 r5 ~9 l8 O) W
    fi9 H% p. C$ L: Z2 ]. z

0 ]; U% ?5 Z# w4 k7 b- E, O    if [ "$DATE" != "" ]
! ~6 g4 Y' y. l" j, [, K) L+ N: }    then
: P. ~& e. M. d) d, `% F! m) B7 [        echo "   date: $DATE"
, V' j1 C1 p+ @8 a* [    else- a* X, B: ^0 w/ s
        echo "", k/ [( E+ b: {5 _
    fi
( [2 M8 _9 Y3 Z4 w$ z+ x}
3 r) h7 Z! ~. q3 }* }
& I2 y5 i9 C; [0 ]9 ^) W; F1 Uwhile getopts "hup:d:" opt; do' N# h" \( q" z% L4 C1 _
    case "$opt" in. h2 E+ S; Q# z1 C0 z; y5 T1 e
        h)
) q  W' I% P0 y5 L            usage
$ Z  R" {" u* H6 c7 d) k( k# O3 ~/ o            exit 0
" N) }6 I& v0 o$ X            ;;5 g! J0 q$ k, l( F
        u)( q* v. k  {* q, V: ]( r
            UPCASE=true
$ i3 x6 }0 D+ O, _0 N8 A0 d: w            ;;, Q5 {6 _/ v& |8 _
        d), d/ i/ n/ }5 m7 a- K
            DATE=$OPTARG0 S+ o9 o2 F9 ]" h* ?0 _$ {
            ;;
' q) N9 {& O3 \2 u$ a/ {. h) Z        p)- ~1 N; t! _) Z2 P# ]
            print $OPTARG& V( c1 u+ f' e: t8 k
            ;;' i+ O. k2 \7 W( q$ C- l! d
    esac. d0 X' h& D9 [6 {
done
: v4 f& k: R! a: I测试上面的脚本如下:& T$ n+ O- [* ~' c8 a2 `
; t$ Q* `* s9 Y, J
bash-3.2$ chmod +x cli-test.sh$ U1 D: {3 @' M' c9 H
bash-3.2$ ./cli-test.sh -h
6 m! y) H4 z5 u0 |USAGE:
3 B7 y2 G) G; |. z: ecli-test <options>
& A1 z! v' Z: ?  ~& ~" F    -h      : print help
" O2 w% S' Z2 z* F1 F. @4 L    -u      : print info upcase0 O( a0 Q: h7 y
    -p <xxx>: print info. Z( u% \1 |0 k& T4 q
    -d <xxx>: date in print info$ T0 ]+ j) o+ O4 `
bash-3.2$ ./cli-test.sh -p hello. Q- \" A3 ]* |# m# G
hellobash-3.2$ ./cli-test.sh -p hello! ?. k3 O8 `- c: G! W
hello; r3 i2 i) z* n- m. X' Z# w
bash-3.2$ ./cli-test.sh -u -p hello# B  |# I/ R; V/ L8 }0 W
HELLO
: H: V$ C, J, k2 Y( P8 o$ tbash-3.2$ ./cli-test.sh -u -d 2016-10-13 -p hello7 W% a2 J7 b0 H3 Z, l1 p7 U
HELLO   date: 2016-10-13
. `1 p$ g- Z# A  _. r  K% P1 A: v1 o' r0 G  }' G- a
, a& `- A1 q. A7 ^1 Q  Z* A
参数自动补全
% E. g% B5 P* }/ }! r参数的补全一般来说比较简单,因为一个命令行工具的参数一般都是固定的。 下面的参数补全脚本是针对 上面的 测试补全的脚本 cli-test.sh
1 D- s; b, K1 X) z/ F9 c+ G2 Q  w0 e$ d3 s1 e+ ?' `
_complete_func() {
9 \4 D- F# l. M$ j3 A/ E$ t5 s    local cur prev opts base  ~( V/ \* R6 T! S- D
    COMPREPLY=()
# C3 u+ X5 e1 k) j8 O6 M+ y    cur="${COMP_WORDS[COMP_CWORD]}"4 Z( a4 i5 E% K# Q! \7 j1 i' Y  R
    prev="${COMP_WORDS[COMP_CWORD-1]}"* P" a9 N/ J- j5 G

/ @* X+ B! W  }! G9 N# ^    opts="-h -u -d -p"" Y! y1 ?, {5 z  X

9 Z% a  g% X: x& N. v/ U    if [[ ${cur} == -* ]] ; then: D2 e# Z( n& m: t$ A( u$ }
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )6 O" r+ t: c. z) u: i
        return 0/ D, P8 Z  L0 @6 u
    fi
3 d! ?0 V4 ^( s8 B) i! ?* P% M+ Y
; _, Q: ~5 T4 f- u, K( H}
. F) t) O1 `8 x# r& d# ^
" m7 y/ J2 n' o; I, _# u* fcomplete -F _complete_func cli-test.sh
, n4 y9 q4 N0 {7 D) }3 Y3 s让自动补全脚本生效的方法如下:0 k" G% r) w8 Y' [# L; v

; w9 k. W( i/ t6 `8 qbash-3.2$ source bash_complete          # 使自动补全脚本生效
+ ?9 w' u' Z  ]/ M( A- Zbash-3.2$ ./cli-test.sh -<TAB><TAB>     # 这里输入 - 之后,再输入2次<TAB>就可以把所有能补全的参数列出来+ A' N) X0 f" E+ g$ W7 ?% T0 p% l

' M4 z2 ^3 I* q4 O3 L0 X+ h: o9 C: l$ C1 M7 v$ Y
; W' ]0 c$ z7 r. `+ }& Z0 U2 g
自定义补全. p0 T) v/ j5 V3 L* \. G. V% P
上面的补全是补全固定的参数,简单,但是用处也不大,用户记不住的其实就是那些会变的参数内容。 下面尝试动态补全 cli-test.sh 的参数 -d 的内容(内容是当前日期以及前3天和后三天的日期) 修改 bash_complete 脚本如下:% t  d, s. d% }. b* Z' H( N
/ A- Q+ i- W9 E, }
_complete_func() {) d6 V3 {4 l. x( }, M. N0 Y
    local cur prev opts base! a, J# W9 ~0 z+ p
    COMPREPLY=()6 w. Q: P  _; w' B% \' [' q
    cur="${COMP_WORDS[COMP_CWORD]}"
: _: g  g; \) U( c, _    prev="${COMP_WORDS[COMP_CWORD-1]}"$ g5 G8 f  m1 M/ ~4 [

8 R# T" c  {, _8 c& L( Y    if [[ ${cur} == -* ]] ; then
+ l& k' c+ `: U+ a        opts="-h -u -d -p"
/ }. h4 U; k- ~( x& |0 |& n: ~( @        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
* d" W7 S8 [. W* {- w( Q    else
6 V. E) Y9 ^! A        opts=$( _complete_d_option )
% W% ?! x$ N1 N" }: F9 A        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )/ N! B7 w$ j0 P! j$ H+ W3 b
    fi# Q+ m# m! k- e- ]/ s

1 R7 O2 a% [7 `; F    return 01 j$ S2 ?3 V) L) U9 E6 s' X
}7 J) f( H7 g% D8 Y* s: x4 U
+ x1 N( ~" h% n1 x
_complete_d_option() {3 l" `( E( c0 T" i5 e# V
    date -v -3d +"%Y-%m-%d"
4 ^$ O6 m! [4 B6 j! r3 I2 B    date -v -2d +"%Y-%m-%d"" U# \6 `7 m  t) X4 j9 G+ I# j# ]
    date -v -1d +"%Y-%m-%d") a' t% W  I* ?) w3 a4 c2 N! B
    date +"%Y-%m-%d"
3 a2 a6 h8 M& H/ U  `0 M    date -v +1d +"%Y-%m-%d"/ f, c: M- h2 T& R( ]
    date -v +2d +"%Y-%m-%d"
+ D+ s! `0 A# v" l    date -v +3d +"%Y-%m-%d"
+ I2 ^7 \! b: f+ {}# h7 _+ O* ^* \" m# n
5 o$ V. L8 \, F. [" Z# I4 G
complete -F _complete_func cli-test.sh$ ~0 E) r+ h9 `1 }! f
测试动态补全的效果
7 H. \. Q9 Q5 B1 {- b3 o( D  A4 n# G/ g
bash-3.2$ source bash_complete          # 使自动补全脚本生效7 s5 R* Z9 K8 n) j$ E. D# ^; w' H
bash-3.2$ ./cli-test.sh -u -d 2016-10-1<TAB><TAB>   # 这是 2016-10-13 执行的结果,其他日子的结果会不一样
  L' c, g( i; m$ j! V0 L- z2016-10-10  2016-10-11  2016-10-12  2016-10-13  2016-10-14  2016-10-15  2016-10-16% h  G% G) c, C) X2 X- {* x
上面就是动态补全,_complete_d_option 函数就是用来实现动态补全的。
& `! _0 T6 z4 j- W  ^# b, k- F% u4 [4 a# S4 l1 J% ^2 r

8 d2 C( ?3 N3 o# R: \zsh 自动补全
, B  F2 d2 B6 S  k相比于bash,zsh 的补全机制更加强大,也更加直观。 同样,下面也通过例子来演示如何在 zsh 中实现上面 bash 中同样的补全功能。
* d$ ^$ L3 z/ d$ q9 k$ P) y- R5 o% m# ^6 z
: _! f( s' S# [  I: i
参数自动补全
; x0 U1 c$ n5 m" I- l相比于 bash 的自动补全脚本,我觉得 zsh 的补全方式更加直观。, g. u! h# s) {, v; T2 ]

( Y( k6 a7 b/ J5 f3 q$ i' }#compdef cli-test.sh
$ g5 R8 D/ M* f) |1 w# Z# filename: _cli-test.h4 m; r" H, n6 q$ S; g2 ^3 D

( R. k1 H0 S0 r7 e0 C$ B. y  T; O_cli_test() {0 a! z% l  F8 k
% u% J3 t9 j3 y3 }  W1 {
    _arguments -C -s -S \
) g0 J4 B6 D) m" S               '-h::' \
9 l) n8 J8 U' M- Q               '-u::' \
- P3 ^% R6 T- L, F! L" s( g: K- V               '-d::' \
3 }0 P3 V; z" Z0 {; C! U6 o6 |               '-p::'
# P* U# l% `. E% n, {# n9 \7 h}! j* N6 a7 V2 S# x- n7 }+ Z' }

% }4 `6 c7 W2 p- J6 r_cli_test "$@"
' z  u( j) T. X0 w3 dzsh 中有个 fpath 的内置变量,将自动补全脚本放在 $fpath 中,或者在 $fpath 中创建指向自动补全的脚本的软连接都可以。 下面是我的环境中 fpath 的值
& K5 s. q; Q; G& o+ G) h4 F! I, G& c* i3 ]
$ echo $fpath
: K& }" }4 Y6 `+ i/usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/5.0.8/functions% }$ S. j0 o  g# d0 Q1 A, k
为了测试 zsh 下自动补全是否有效,我在 fpath 下给自己的自动补全脚本创建了软连接; N+ U9 t7 l" ]
8 _' D* |. k/ d+ g
$ cd /usr/local/share/zsh/site-functions
2 {" Q% V: z0 [" h9 U$ ln -s ~/projects/bash/autocomp/_cli-test.sh _cli-test.sh1 T9 r  O, [1 i  P  M  @
测试结果
- h$ Y  D4 h+ T2 ?. I- j* i" E0 [" w0 I8 I, o) J+ ]
$ ./cli-test.sh -<TAB><TAB>
  R+ P1 N* H. b. [0 f( z0 ^-d  -h  -p  -u, u5 |! G; o' U  l9 v& j; n# e! W0 k
可以看出,zsh 的补全方法非常简单直观。稍微解释下上面的代码% `' y3 K& o0 ~: @

) Z, ~4 {" \( j, E_arguments
( U* o& Z- B; m/ h9 [# k. v( z这个函数是 zsh 自带的,有点类似 bash 中的 compgen ,但是功能更加强大。
- Y8 m, N" X( _# w6 @- F/ v+ H& v
) T2 N" G' b' v'-h::' \
% z( |3 [# J+ b6 E" Z这里 : 分割的3部分分别是 “待补全的参数:参数的说明:动态补全参数的内容“, T2 G* P3 k$ o" e$ S3 R# H

0 b$ O/ G  T3 K4 f: H) l" u! m9 t: W6 ^' D0 X
自定义补全% l: d4 a4 V9 j# t: y7 M0 Q
根据上面的解释,要想动态补全 -d 参数非常简单,只要加个函数,并配置在 -d:: 之后即可
9 }& U) m& R" k" b, c3 G8 u+ c+ F; U4 w7 d1 S( c
#compdef cli-test.sh
' o% {5 {) N- ?* R# filename: _cli-test.h
* ?+ z: {+ K: T1 ?( l7 C  C' m! Z; K; B
_cli_test() {! w, j* \8 o7 g8 R3 B
' J9 T6 @. e" }% T/ V& S
    _arguments -C -s -S \; l. f9 D4 @/ K9 u* B/ G
               '-h::' \- [# G9 _3 D$ E0 R% _& E. w: k
               '-u::' \! ~" ]: E! T5 U/ \9 k( A% r
               '-d:auto complete date:__complete_d_option' \; U+ m: M) F+ S  n2 h( \0 e
               '-p::'' ~5 W& |6 t4 u
}! s8 s4 f+ {. q, y2 ^

' ~8 M! f8 ?4 |6 S__complete_d_option() {
" O6 i; Q; @3 A9 J1 V* z    local expl
7 L2 U( [  G' T- c# n. z. K0 \; c    dates=( `generate_date` )
+ s7 P. m; k9 S% b* g4 Q
6 w: [" H! l! E4 S, v  G* x    _wanted dates expl date compadd $* - $dates
& b& b5 ]7 I  H6 N1 e+ S}
* r; @7 m& M; A, G" m, V5 w" q/ W  R0 J4 ^# r0 l7 @* m$ `0 v9 t
generate_date() {
& V) _& e" U+ Q# K8 R    date -v -3d +"%Y-%m-%d"3 `+ |% v# E4 L/ b1 K5 ]  [9 h
    date -v -2d +"%Y-%m-%d"# ?$ I* ^5 a3 @# F: q
    date -v -1d +"%Y-%m-%d"* A. X# ~! c, S2 T- L' R) C* h& n
    date +"%Y-%m-%d"
. O1 Y9 h. o! `; n: ~6 N9 q8 D2 A/ o    date -v +1d +"%Y-%m-%d"
( c% S5 t0 r/ J- ], s) Q! j    date -v +2d +"%Y-%m-%d"
* w# k, k$ t3 I" S; S% I1 i! j    date -v +3d +"%Y-%m-%d"1 R* `) s3 W6 y) |0 ?; `/ R7 C
}% R- D% P% {7 i& Y0 W

6 |7 d4 D* o$ F$ R! W_cli_test "$@"! M3 s! O; z' J0 r4 \& G8 w7 I
测试动态补全的效果
; h- I- x9 M- L/ ^7 W# ]: y  ^: d# a5 V4 O5 b
$ ./cli-test.sh -u -d 2016-10-<TAB><TAB>
+ P; j' n2 x5 M" \; {5 m( P2016-10-14  2016-10-15  2016-10-16  2016-10-17  2016-10-18  2016-10-19  2016-10-20+ K: m5 A; i; X

, f$ q  l/ e8 X) b6 O$ q* ^# h3 v3 H% ?; A9 D. H
总结
# m2 Z) k( d9 L  |5 n2中shell环境下的自动补全都介绍完了,它们自动补全的机制都不难,只是 zsh 毕竟是新一点的shell,补全方式更加简单易懂。 特别是对于存在子命令和复杂的参数补全,以及参数内容动态补全的情况下,zsh 的机制更加易于维护。
. U2 }6 i% U! m8 b' `
  • 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 01:46 , Processed in 0.140625 second(s), 24 queries , Gzip On.

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

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

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