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

命令行自动补全原理

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
8 v# m% h; U) M0 ]
概述, D- o" J+ L% o! u
bash 自动补全7 A5 u. S) V) y/ \3 A) x$ d
  • 测试补全的脚本
  • 参数自动补全
  • 自定义补全& ]4 \5 f6 k; H% W2 G- n
zsh 自动补全# o& T  v1 A! H" }4 }
  • 参数自动补全
  • 自定义补全! t: q; O  q* n9 M: ?" m2 H: A
总结
/ j' q3 C9 q5 e! Z& h! G* c  s( c
/ i2 Y2 W% L! H% f概述
6 q8 F7 O/ H( m, K4 {虽然CLI(命令行)类型的工具由于其高效,易定制的特性为很多人所喜爱(也包括我自己), 但是,相对于GUI工具,CLI工具给人的直观感觉就是不容易使用,如果看到工具中大量的参数说明后,更让人望而却步。
3 F# e# w- j8 z; B. ]0 M$ F4 ~1 |0 E# Y$ L2 f& A+ T! A
因此,如果在自己命令行工具中加入 自动补全 的功能,就可以极大的提高工具的易用性,还可以保留命令行工具原有的高效。 这里所说的 自动补全 不仅仅是补全那些固定的参数(这些意义不大),更多的是补全动态的内容。# P! m/ E: U3 o# N  p. B# v

% @2 h2 n/ `) ^; h1 t9 x本篇主要介绍两种主流的 shell(bash 和 zsh)中,如何实现命令行工具的补全。9 E5 [- V2 \4 V+ W+ @7 M+ C
2 Z1 r8 a6 ]# _# n) u
bash 自动补全
; h) g9 ^. P4 ?2 M测试补全的脚本. F" B4 q' n4 C5 i
简单编写一个测试脚本用来测试后面的自动补全:. D+ _6 [/ g; ^+ K" X

7 |2 L3 \" ]/ V3 n6 ~#!/bin/bash
" |% w: f( y7 g" }3 d7 y" X# filename: cli-test.sh
4 r- N- M& O, l# Q# W3 [8 \
( d2 f/ r& C# EUPCASE=false# h7 E* m, O5 v" j4 k
DATE=""
; g* L9 ~3 A$ _9 S" }8 X7 h. H) U$ b9 a3 ~
usage() {% e7 L. G8 p/ B9 B- n7 R
    echo "USAGE:", I2 y7 @7 E8 d( J7 I
    echo "cli-test <options>"
% G; K9 N7 q3 C3 ]2 q0 _    echo "    -h      : print help"
. O, L4 }! W; b" s; `    echo "    -u      : print info upcase"& H( |# c- P+ z
    echo "    -p <xxx>: print info"' ]. j/ H! h" M/ n- Q! L  r  b
    echo "    -d <xxx>: date in print info"
# N3 ^5 L  _6 n: Y7 Y}
1 |9 L7 _2 J' @: @5 }- ^; H& l6 Z2 ], ^; }; }' [
print() {, P3 R7 a$ f% C) @. C3 @, b. x% h. Z" v# w
    if $UPCASE
* X. O. J$ P$ I/ \  Q    then
6 q. }5 R- t3 N# k       echo -n $1 | tr a-z A-Z
5 F% t. r5 _! G7 _& D6 O3 E    else
; C) x  c& n! O4 B" m; C        echo -n $17 K1 L" G4 s# o6 v* a# ]
    fi+ f( `. f' v. J) Z

. I# b; |9 V7 I  h& v    if [ "$DATE" != "" ]3 p3 K. E7 ?3 I  n, q
    then# \" q+ U3 p1 h+ C) b7 {
        echo "   date: $DATE"& F, n0 \0 c4 c" \! ^/ U3 ], I. g
    else
- v$ `! F$ |# Q* l/ i! F9 }        echo ""3 {2 Q2 o/ \0 ~4 |  d
    fi3 k% z* L) A* ?4 z5 h
}
2 h+ r, o! p! C. N. `* u+ |$ ]+ E3 m7 K. S  v% X% Z8 @
while getopts "hup:d:" opt; do
: l" w: e- _+ {% [    case "$opt" in" T% R* N# u  W. ^7 _: N
        h)
, x* o4 k) i6 b  H            usage
* M% ]- _' G7 Y! ]            exit 0
3 d$ I0 ]! K* L5 M5 F            ;;3 i' s) P! G% ]/ D1 c
        u)
8 C4 j% ^2 q/ }- o+ S' I. n4 J( q            UPCASE=true4 E" B# [$ ~( ^8 U6 }% g
            ;;
. T- V2 }6 E. d6 {3 b  P) H        d)
4 f0 j% t4 W) a1 i5 j: y5 `) Z& k9 ^            DATE=$OPTARG+ l9 X7 h; i+ O9 p6 [
            ;;( Z0 ~5 [( f6 h2 P1 L
        p)
$ L4 ~' T+ `1 Q, v6 V. h            print $OPTARG- Y8 k5 ~$ @* e( o0 Q! S! o1 t$ O
            ;;
3 l* _+ {0 R" b    esac
& O4 @& @( k5 E, }$ gdone6 y, C7 A& l; g# J8 P
测试上面的脚本如下:- h& _# X. \: f' W& [

; ~7 L) O. g9 ~+ U; p1 {, jbash-3.2$ chmod +x cli-test.sh0 ?6 n6 l3 L7 k9 s- Q( x: p2 T
bash-3.2$ ./cli-test.sh -h3 g0 }/ w* G6 z! ]
USAGE:. ?: x* _8 Q$ m4 u: ?" Z6 V$ Q
cli-test <options>
( i. v$ U9 ^3 Q0 Y, Q6 R    -h      : print help
, Y- z* p, J9 o& e4 R, x5 x    -u      : print info upcase
: r( P2 z4 ~3 u6 R( y    -p <xxx>: print info
% i7 r5 n1 |5 `' ]: s    -d <xxx>: date in print info
( U( ?' A- t$ L3 h6 l  [bash-3.2$ ./cli-test.sh -p hello; i" I: Q2 \! M5 {3 O
hellobash-3.2$ ./cli-test.sh -p hello
" j3 k4 C) V8 ~$ S1 whello
; D- n  J/ g* S- Hbash-3.2$ ./cli-test.sh -u -p hello' \" m% b) u; G% U
HELLO
+ k  _1 Q* g1 O6 }) Hbash-3.2$ ./cli-test.sh -u -d 2016-10-13 -p hello
9 l/ `+ l) v9 e* }* Z: @; THELLO   date: 2016-10-13& X+ O/ i. o2 g; K  j' d

6 g3 t6 R3 M1 P  h
. S  ^# r& Q5 u参数自动补全
3 |- R* M( W) s. a$ z# t3 _7 _参数的补全一般来说比较简单,因为一个命令行工具的参数一般都是固定的。 下面的参数补全脚本是针对 上面的 测试补全的脚本 cli-test.sh4 N% n, o5 K: _3 X
( l7 ^" t; H6 ^1 P1 W
_complete_func() {! X, J! _5 k  @" ~' R: `" L2 ^, I
    local cur prev opts base6 A- \5 |0 i' g/ c% b4 ^; R& I4 @. \
    COMPREPLY=()0 d, r' e; Q* v! z, w$ D
    cur="${COMP_WORDS[COMP_CWORD]}"% O1 O8 P4 a& F4 u) M6 h1 {
    prev="${COMP_WORDS[COMP_CWORD-1]}"/ u) S: }2 |! x- s5 T7 t4 G

/ c1 K  ]% c7 H7 U    opts="-h -u -d -p"
( }' t  V) Q5 A
! o$ M/ |$ H4 j: {7 }2 @    if [[ ${cur} == -* ]] ; then+ `% r* s2 t* W; u
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
2 l0 y) w8 t9 j        return 0
7 E0 q) k; `1 i7 W+ f    fi0 e5 s* x  G/ w; p5 `  H
* O9 F+ P& i. _1 ?
}9 k1 `; E) Q+ c+ \

+ q- f- L7 b& W5 `% Scomplete -F _complete_func cli-test.sh9 n8 P0 F1 b- [1 R; U3 ]
让自动补全脚本生效的方法如下:* g( X: y3 M* O; J
! s3 M: o' o6 c3 W
bash-3.2$ source bash_complete          # 使自动补全脚本生效1 c5 `  i$ S0 B4 G3 }
bash-3.2$ ./cli-test.sh -<TAB><TAB>     # 这里输入 - 之后,再输入2次<TAB>就可以把所有能补全的参数列出来$ x  H% U+ |/ P

3 H, x4 Z( ]4 i' S( v& N3 u; x1 u. f2 q

/ K# s1 ^" A6 R) o3 n自定义补全
6 i7 Q/ ]8 Q8 Z上面的补全是补全固定的参数,简单,但是用处也不大,用户记不住的其实就是那些会变的参数内容。 下面尝试动态补全 cli-test.sh 的参数 -d 的内容(内容是当前日期以及前3天和后三天的日期) 修改 bash_complete 脚本如下:6 e! q. N8 l- k0 r# ~. p# A6 T, h
+ U% o( N0 J5 \! h. h
_complete_func() {
1 K& Z! m2 ]$ [, g. |    local cur prev opts base
1 ^5 R1 k# k% Q; P4 c! E) n    COMPREPLY=()
( ~" ?% d7 ]$ B  @    cur="${COMP_WORDS[COMP_CWORD]}"
$ ?9 i5 H2 e) B$ q4 Z/ x    prev="${COMP_WORDS[COMP_CWORD-1]}"
0 t; _; }4 C5 K# d
3 [& {3 X8 n3 V( U: x    if [[ ${cur} == -* ]] ; then
) h  n  P' A) Z        opts="-h -u -d -p". u% J3 t/ A! h# k& p9 h3 L
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )* A% c; a  [( O# D6 ?4 C1 ^
    else5 ~4 ~/ ?& x3 V0 s( F
        opts=$( _complete_d_option ); l5 [( Y8 k+ J( D6 W; }
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )  y0 Z& v0 U, M0 W2 J3 Y
    fi
/ k8 e& `$ X5 @5 \8 F$ g+ ^
6 u; j6 n. M+ W) m" G( O. N    return 0+ s7 M1 p4 M7 ?3 e" ?0 J% g
}
* ^% e4 F4 U5 E" u
1 i: E+ l+ f- y, J  m_complete_d_option() {
" E3 r1 k% t# B4 Q    date -v -3d +"%Y-%m-%d"
" q6 e0 h/ W" W7 }1 u5 J    date -v -2d +"%Y-%m-%d"
" y: Z/ u; Y  y- e0 R( m# Z* ~) Q    date -v -1d +"%Y-%m-%d"* e' t" m/ x: Z9 E+ T9 z9 L' D
    date +"%Y-%m-%d"6 l9 D% W! O7 e7 i
    date -v +1d +"%Y-%m-%d"0 m' ?1 U6 ^; `1 ?6 A& t( b
    date -v +2d +"%Y-%m-%d"* W  u, g! |6 Y3 b- o$ `
    date -v +3d +"%Y-%m-%d"
& M( e# @; c( H6 |, ?}7 d! |# ^4 i) J  V( p3 X( o3 h- ]: W/ O
8 @5 H6 ]  _" g' j& _
complete -F _complete_func cli-test.sh7 @1 O( S0 ]/ z% a4 Z
测试动态补全的效果
, A9 _' E4 U: N. x0 A
" O) s1 m& N  c' E, m# ibash-3.2$ source bash_complete          # 使自动补全脚本生效5 y1 k! g9 a# I8 O; _$ n
bash-3.2$ ./cli-test.sh -u -d 2016-10-1<TAB><TAB>   # 这是 2016-10-13 执行的结果,其他日子的结果会不一样8 V, N9 I3 ^/ G  z0 z
2016-10-10  2016-10-11  2016-10-12  2016-10-13  2016-10-14  2016-10-15  2016-10-161 Y- c6 ?" z9 V7 q) }0 G
上面就是动态补全,_complete_d_option 函数就是用来实现动态补全的。3 |4 N. z5 A  k
: y# q! j8 W9 f9 {7 @
, g: o4 p( J9 R
zsh 自动补全
7 e( Z- o/ _; L9 L' c相比于bash,zsh 的补全机制更加强大,也更加直观。 同样,下面也通过例子来演示如何在 zsh 中实现上面 bash 中同样的补全功能。
0 z1 D0 u* o. k, i; G1 R6 T% k
4 L. I& Q0 m& B, J8 p" F3 P5 S; }+ q/ Y1 f  F" j; q
参数自动补全1 ^8 L1 x# J9 |* c
相比于 bash 的自动补全脚本,我觉得 zsh 的补全方式更加直观。. ~+ R9 [5 K; }. l! B( P' n1 I
4 F/ N, S% W& A6 i
#compdef cli-test.sh
# S( z/ {$ \0 N0 p- D2 c4 C# filename: _cli-test.h
! k" d7 i0 R( @0 ^2 L. B) u( K
. b. K& g9 h  l4 v_cli_test() {  P/ L4 q. T/ o, T$ Z# b3 v4 j
( p+ r/ u1 C2 m, y
    _arguments -C -s -S \% [" \7 h' o$ F% h
               '-h::' \
( |4 }8 T0 {0 _2 Y  c               '-u::' \' K# w5 P* V. j+ n2 [4 _: @; j" @( U
               '-d::' \
6 c- E3 V# e. V1 w0 k" T1 p% K3 Q               '-p::'
  P" f0 ?& B) k}6 P( H0 }% b1 _2 P4 t

- ?  T# q8 e3 c) S1 Q_cli_test "$@"% c. O5 U- ]# V8 Y, l6 E
zsh 中有个 fpath 的内置变量,将自动补全脚本放在 $fpath 中,或者在 $fpath 中创建指向自动补全的脚本的软连接都可以。 下面是我的环境中 fpath 的值
+ _4 _' O2 A9 Y* c: E7 K/ e
, |) _. k; s. Y3 T. F9 g' z$ echo $fpath( K" g/ C0 [% G0 N
/usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/5.0.8/functions
3 s: O: m& d! g$ m, ^$ J为了测试 zsh 下自动补全是否有效,我在 fpath 下给自己的自动补全脚本创建了软连接- S; J" D5 `- a" D: u4 R
: r: y, W. \& P, Q8 o$ I
$ cd /usr/local/share/zsh/site-functions
# ^3 u; |+ P+ K) P; ?$ ln -s ~/projects/bash/autocomp/_cli-test.sh _cli-test.sh
0 f. E+ h6 Z* E7 l+ C' R" g" w; r测试结果
$ ^3 D2 D! H& S% b/ q1 J# G, q8 x
$ ./cli-test.sh -<TAB><TAB>
9 T0 @6 E& P1 m9 H+ d-d  -h  -p  -u
3 x7 o4 @/ _) M可以看出,zsh 的补全方法非常简单直观。稍微解释下上面的代码: Y; b6 l2 [- j- Y; a2 f
0 R) S  n4 \6 e; C
_arguments
/ B# A6 C# Y! W9 e9 h7 _$ h这个函数是 zsh 自带的,有点类似 bash 中的 compgen ,但是功能更加强大。8 p/ d$ ^0 F# v

6 S" ]) B: ~1 h0 u" ~'-h::' \
4 e! m  d/ J' D1 r2 ?! C& @这里 : 分割的3部分分别是 “待补全的参数:参数的说明:动态补全参数的内容“
% M" e! T# _( @/ ]  ~; [) C6 y
8 t0 ]; B4 V1 t- I' b# M
& e: f9 X/ S" F" r自定义补全# j8 Q( F2 A! c: ^. T/ B1 j, c
根据上面的解释,要想动态补全 -d 参数非常简单,只要加个函数,并配置在 -d:: 之后即可
: p3 U/ T1 }  T. d1 @8 c. {; a; O) ^# K( l
#compdef cli-test.sh
' a0 `! y% v+ F4 N( s# filename: _cli-test.h
* F+ |4 U" l2 \9 Y6 }1 J. ]
6 a5 m- [" I; S; R. V8 I_cli_test() {0 p, g9 U8 T1 X4 I

9 @% j) o* ?6 n1 B% {. Z, I5 w    _arguments -C -s -S \+ R4 \6 v! v1 L% d* {
               '-h::' \
& g* H# r# Z8 \  M  Y. f6 {               '-u::' \
4 b% D% `/ o: W3 o# {               '-d:auto complete date:__complete_d_option' \9 u$ _( H+ M1 o% w8 J$ J& [
               '-p::'% i+ F' \  A/ b# T
}
# O: R" ^1 Z, E/ U; n
9 r- e: W. A7 J' d& E, L: T. l__complete_d_option() {' F0 L3 C8 z1 w7 V4 y( L
    local expl. E) c7 w) z2 r/ b" d
    dates=( `generate_date` )$ N! R5 u% |; A, |! _1 r8 X& M  @

: h: O: k( s2 V$ W1 V0 q+ j5 u    _wanted dates expl date compadd $* - $dates8 ?$ ^& I) ]  `
}
, @' o/ `: K+ X4 v5 O
  R, R1 T, F$ ?( N" F" ygenerate_date() {- T% h2 `# n, U* p( N
    date -v -3d +"%Y-%m-%d"
# v% R! y% Q4 d/ e9 P    date -v -2d +"%Y-%m-%d"# N# n" b8 M) E) C& v8 I
    date -v -1d +"%Y-%m-%d"9 c. p' N' q6 E/ i6 @' }8 p* l
    date +"%Y-%m-%d"
) n# \! ]0 k5 f$ o    date -v +1d +"%Y-%m-%d"$ {1 M% A! P+ F+ L0 [
    date -v +2d +"%Y-%m-%d"8 N% ], t( b. i- N+ j
    date -v +3d +"%Y-%m-%d"
/ o2 r+ `$ D. q- B}: T2 o, `8 y, {/ A. A
  Z/ ?7 ^. e- d  c
_cli_test "$@"1 x) l% B2 q; q! d/ M
测试动态补全的效果. h( _; B$ d, j1 M! p
5 E9 m  Z, C$ R
$ ./cli-test.sh -u -d 2016-10-<TAB><TAB>& p2 t! |. y$ I! \% D9 Z
2016-10-14  2016-10-15  2016-10-16  2016-10-17  2016-10-18  2016-10-19  2016-10-20
9 G  L8 d" c, O: j
$ n& w; }, i. m5 P, a& A/ Z2 a9 g5 B& W' W7 n, K+ P
总结; ?: A9 ^: H6 t9 ^
2中shell环境下的自动补全都介绍完了,它们自动补全的机制都不难,只是 zsh 毕竟是新一点的shell,补全方式更加简单易懂。 特别是对于存在子命令和复杂的参数补全,以及参数内容动态补全的情况下,zsh 的机制更加易于维护。/ s4 F" w) l8 _: _
  • TA的每日心情

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

    [LV.1]初来乍到

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

    本版积分规则

    关闭

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

    EDA365公众号

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

    GMT+8, 2025-10-26 16:18 , Processed in 0.140625 second(s), 23 queries , Gzip On.

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

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

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