• 命令行自动补全原理


    概述

    虽然CLI(命令行)类型的工具由于其高效,易定制的特性为很多人所喜爱(也包括我自己), 但是,相对于GUI工具,CLI工具给人的直观感觉就是不容易使用,如果看到工具中大量的参数说明后,更让人望而却步。

    因此,如果在自己命令行工具中加入 自动补全 的功能,就可以极大的提高工具的易用性,还可以保留命令行工具原有的高效。 这里所说的 自动补全 不仅仅是补全那些固定的参数(这些意义不大),更多的是补全动态的内容。

    本篇主要介绍两种主流的 shell(bash 和 zsh)中,如何实现命令行工具的补全。

    bash 自动补全

    测试补全的脚本

    简单编写一个测试脚本用来测试后面的自动补全:

    #!/bin/bash
    # filename: cli-test.sh
    
    UPCASE=false
    DATE=""
    
    usage() {
        echo "USAGE:"
        echo "cli-test <options>"
        echo "    -h      : print help"
        echo "    -u      : print info upcase"
        echo "    -p <xxx>: print info"
        echo "    -d <xxx>: date in print info"
    }
    
    print() {
        if $UPCASE
        then
           echo -n $1 | tr a-z A-Z
        else
            echo -n $1
        fi
    
        if [ "$DATE" != "" ]
        then
            echo "   date: $DATE"
        else
            echo ""
        fi
    }
    
    while getopts "hup:d:" opt; do
        case "$opt" in
            h)
                usage
                exit 0
                ;;
            u)
                UPCASE=true
                ;;
            d)
                DATE=$OPTARG
                ;;
            p)
                print $OPTARG
                ;;
        esac
    done
    

    测试上面的脚本如下:

    bash-3.2$ chmod +x cli-test.sh
    bash-3.2$ ./cli-test.sh -h
    USAGE:
    cli-test <options>
        -h      : print help
        -u      : print info upcase
        -p <xxx>: print info
        -d <xxx>: date in print info
    bash-3.2$ ./cli-test.sh -p hello
    hellobash-3.2$ ./cli-test.sh -p hello
    hello
    bash-3.2$ ./cli-test.sh -u -p hello
    HELLO
    bash-3.2$ ./cli-test.sh -u -d 2016-10-13 -p hello
    HELLO   date: 2016-10-13
    

    参数自动补全

    参数的补全一般来说比较简单,因为一个命令行工具的参数一般都是固定的。 下面的参数补全脚本是针对 上面的 测试补全的脚本 cli-test.sh

    _complete_func() {
        local cur prev opts base
        COMPREPLY=()
        cur="${COMP_WORDS[COMP_CWORD]}"
        prev="${COMP_WORDS[COMP_CWORD-1]}"
    
        opts="-h -u -d -p"
    
        if [[ ${cur} == -* ]] ; then
            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
            return 0
        fi
    
    }
    
    complete -F _complete_func cli-test.sh
    

    让自动补全脚本生效的方法如下:

    bash-3.2$ source bash_complete          # 使自动补全脚本生效
    bash-3.2$ ./cli-test.sh -<TAB><TAB>     # 这里输入 - 之后,再输入2次<TAB>就可以把所有能补全的参数列出来
    

    自定义补全

    上面的补全是补全固定的参数,简单,但是用处也不大,用户记不住的其实就是那些会变的参数内容。 下面尝试动态补全 cli-test.sh 的参数 -d 的内容(内容是当前日期以及前3天和后三天的日期) 修改 bash_complete 脚本如下:

    _complete_func() {
        local cur prev opts base
        COMPREPLY=()
        cur="${COMP_WORDS[COMP_CWORD]}"
        prev="${COMP_WORDS[COMP_CWORD-1]}"
    
        if [[ ${cur} == -* ]] ; then
            opts="-h -u -d -p"
            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        else
            opts=$( _complete_d_option )
            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        fi
    
        return 0
    }
    
    _complete_d_option() {
        date -v -3d +"%Y-%m-%d"
        date -v -2d +"%Y-%m-%d"
        date -v -1d +"%Y-%m-%d"
        date +"%Y-%m-%d"
        date -v +1d +"%Y-%m-%d"
        date -v +2d +"%Y-%m-%d"
        date -v +3d +"%Y-%m-%d"
    }
    
    complete -F _complete_func cli-test.sh
    

    测试动态补全的效果

    bash-3.2$ source bash_complete          # 使自动补全脚本生效
    bash-3.2$ ./cli-test.sh -u -d 2016-10-1<TAB><TAB>   # 这是 2016-10-13 执行的结果,其他日子的结果会不一样
    2016-10-10  2016-10-11  2016-10-12  2016-10-13  2016-10-14  2016-10-15  2016-10-16
    

    上面就是动态补全,_complete_d_option 函数就是用来实现动态补全的。

    zsh 自动补全

    相比于bash,zsh 的补全机制更加强大,也更加直观。 同样,下面也通过例子来演示如何在 zsh 中实现上面 bash 中同样的补全功能。

    参数自动补全

    相比于 bash 的自动补全脚本,我觉得 zsh 的补全方式更加直观。

    #compdef cli-test.sh
    # filename: _cli-test.h
    
    _cli_test() {
    
        _arguments -C -s -S 
                   '-h::' 
                   '-u::' 
                   '-d::' 
                   '-p::'
    }
    
    _cli_test "$@"
    

    zsh 中有个 fpath 的内置变量,将自动补全脚本放在 $fpath 中,或者在 $fpath 中创建指向自动补全的脚本的软连接都可以。 下面是我的环境中 fpath 的值

    $ echo $fpath
    /usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/5.0.8/functions
    

    为了测试 zsh 下自动补全是否有效,我在 fpath 下给自己的自动补全脚本创建了软连接

    $ cd /usr/local/share/zsh/site-functions
    $ ln -s ~/projects/bash/autocomp/_cli-test.sh _cli-test.sh
    

    测试结果

    $ ./cli-test.sh -<TAB><TAB>
    -d  -h  -p  -u
    

    可以看出,zsh 的补全方法非常简单直观。稍微解释下上面的代码

    _arguments
    

    这个函数是 zsh 自带的,有点类似 bash 中的 compgen ,但是功能更加强大。

    '-h::' 
    

    这里 : 分割的3部分分别是 “待补全的参数:参数的说明:动态补全参数的内容“

    自定义补全

    根据上面的解释,要想动态补全 -d 参数非常简单,只要加个函数,并配置在 -d:: 之后即可

    #compdef cli-test.sh
    # filename: _cli-test.h
    
    _cli_test() {
    
        _arguments -C -s -S 
                   '-h::' 
                   '-u::' 
                   '-d:auto complete date:__complete_d_option' 
                   '-p::'
    }
    
    __complete_d_option() {
        local expl
        dates=( `generate_date` )
    
        _wanted dates expl date compadd $* - $dates
    }
    
    generate_date() {
        date -v -3d +"%Y-%m-%d"
        date -v -2d +"%Y-%m-%d"
        date -v -1d +"%Y-%m-%d"
        date +"%Y-%m-%d"
        date -v +1d +"%Y-%m-%d"
        date -v +2d +"%Y-%m-%d"
        date -v +3d +"%Y-%m-%d"
    }
    
    _cli_test "$@"
    

    测试动态补全的效果

    $ ./cli-test.sh -u -d 2016-10-<TAB><TAB>
    2016-10-14  2016-10-15  2016-10-16  2016-10-17  2016-10-18  2016-10-19  2016-10-20
    

    总结

    2中shell环境下的自动补全都介绍完了,它们自动补全的机制都不难,只是 zsh 毕竟是新一点的shell,补全方式更加简单易懂。 特别是对于存在子命令和复杂的参数补全,以及参数内容动态补全的情况下,zsh 的机制更加易于维护。

    来源:http://blog.iotalabs.io/

  • 相关阅读:
    async await promise 执行时序
    理解prototype
    X-Requested-With
    event事件传播规则
    【小文】Flask web Hello程序!
    【小文】php-gtk: Hello!
    【小文】HTTP 状态码
    【小文】Python环境安装配置
    C语言:趣味小问题 鸡兔同笼
    C语言:趣味小问题 百钱买百鸡
  • 原文地址:https://www.cnblogs.com/wang_yb/p/5969451.html
Copyright © 2020-2023  润新知