在命令行按下tab键之后, 发生了生么?

这篇具有很好参考价值的文章主要介绍了在命令行按下tab键之后, 发生了生么?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  • 1. 引言
  • 2. complete命令
  • 3. 自定义补全列表
  • 4. 动态补全列表
  • 5. compgen命令
  • 6. 别名的自动补全
  • 7. 补全规则永久生效
  • 8. 自动加载
  • 9. 参考


1. 引言

当我们输入ls 再按下TAB时, 会自动列出当前路径下所有的文件;

当我们输入ls a再按下TAB时, 会自动列出当前路径下所有以a开头的文件; 若只有一个以a开头的文件, 将会自动补全;

当我们输入type 再按下TAB时, 会自动列出全所有可执行的命令;

当我们输入docker rmi 再按下TAB时, 会自动列出所有镜像名;

一个显示文件, 一个显示命令, 一个显示容器名, 这是怎么做到的?

本文将带你一探究竟, 并以docker为例, 实现一个简单的docker自动补全规则

2. complete命令

上述功能, 是 Bash 2.05 版本新增的功能, 叫做自动补全. 自动补全允许我们对命令和选项设置补全规则, 按下TAB之后, 会根据我们设置的规则返回补全列表, 当补全列表只有一个元素时, 就会自动补全.

bash自动补全用到最主要的命令就是complete, 这是一个Bash的内置命令(builtin), 用于指定某个命令的补全规则, complete语法如下:

complete [-abcdefgjksuv] [-o comp-option] [-DEI] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [name …]
complete -pr [-DEI] [name …]

选项:
    -o comp-option
        定义一些补全的行为, 可以使用的行为如下:
        nospace    补全后不在最后添加空格
        nosort     对于补全列表不要按字母排序

    -A action
        使用预设的补全规则, 可使用的补全规则如下:
        alias        补全列表设置为所有已定义的别名. 等同于-a
        builtin      补全列表设置为所有shell内置命令. 等同于-b
        command      补全列表设置为所有可执行命令. 等同于-c
        directory    补全列表设置为当前路径下所有目录. 等同于-d,
                     也就是说 complete -d xxx 与 complete -A directory xxx 等价, 只是写法不一样
        export       补全列表设置为所有环境变量名. 等同于-e
        file         补全列表设置为当前路径下所有文件. 等同于-f
        function     补全列表设置为所有函数名
        signal       补全列表设置为所有信号名
        user         补全列表设置为所有用户名. 等同于-u
        variable     补全列表设置为所有变量名. 等同于-v

    -F function
        用函数来定义补全规则, 函数运行后 COMPREPLY 变量做为补全列表

    -W wordlist
        用一个字符串来做为补全列表

    -p name
        显示某个命令的补全规则, 如果 name 为空的话则显示所有命令的补全规则

    -r
        移除某个命令的补全规则

ls命令默认的补全列表是当前路径下所有文件, 现在, 我们改变其补全规则, 让其补全列表变为所有可执行命令

$ cd /

# 先测试下 ls 默认的补全规则
$ ls<TAB>
bin/    boot/   dev/    etc/    home/   lib/    lib32/  lib64/  libx32/ media/  mnt/    opt/    proc/   root/   run/    sbin/   srv/    sys/    tmp/    usr/    var/

# 修改 ls 的补全规则, 让所有可执行命令作为其补全列表
$ complete -c ls

# 测试修改补全规则后的 ls
$ ls who<TAB>
who                   whoami                whoopsie              whoopsie-preferences

提示: 上述改变的补全规则只在当前shell有效, 即不会影响到其他用户, 重新登录后也会失效. 所以想要恢复ls命令的补全规则的话, 只需要退出再重新登录服务器就好了. 至于如何永久改变补全规则, 请看后文.


我们再来看下type命令预设的补全规则, 发现type命令设置的补全列表是所有可执行命令

$ complete -p type
complete -c type

至此, 我们应该知道引言中所提出的问题, 为什么ls命令会文件而type命令会列出命令

3. 自定义补全列表

尽管Bash预设了很多补全规则, 但是很明显, 如果我们自己想给docker命令写补全规则的话, 预设的补全规则显然是不能满足我们需求的. 所以, 我们可以用-W选项来自定义补全列表.

假设我们自己写了个mydocker命令, 可以使用的功能有mydocker rm, mydocker rmi, mydocker stop, mydocker start, 显然, mydocker的补全列表为rm rmi stop start, 我们可以使用下面的命令来设置补全规则

# 将 rm rmi stop start 设置为 mydocker 的补全列表
$ complete -W 'rm rmi stop start' mydocker

$ mydocker <TAB>
rm     rmi    start  stop

$ mydocker st<TAB>
start  stop

注意: mydocker命令本身没有自动补全, 需要手动完整输入


到这一步, 我们已经能给相当一部分的命令来定义补全规则了. 但是, 上述的'-W'选项, 是静态的补全规则, 不会随着某些条件的改变而变化; docker rmi <TAB>所有显示的镜像名, 会随着镜像的增删而改变; docker rm <TAB>所有显示的容器名, 会随着容器的增删而改变; 是动态的补全规则, 这是如何做到的呢?

我们直接通过-p选项来查看docker预设的补全规则就好了, 发现docker命令是通过-F _docker来指定补全规则; 再通过type _docker来查看_docker是什么玩意, 发现_docker是一个非常复杂的函数

$ complete -p docker
complete -F _docker docker

$ type _docker
_docker is a function
_docker ()
{
    ......
}

接下来, 我们来好好聊一聊-F这个选项

4. 动态补全列表

-F选项会指定一个函数做为补全规则, 每次按下TAB时, 就会调用这个函数, 并且将COMPREPLY的值做为补全列表, 所以我们需要在函数中处理COMPREPLY变量

除了COMPREPLY变量外, Bash还提供了一些变量来方便我们获取当前的输入

变量名 类型 说明
COMP_LINE 字符串 当前的命令行输入的所有内容
COMP_WORDS 数组 当前的命令行输入的所有内容, 和COMP_LINE不同的是, 这个变量是一个数组
COMP_CWORD 整数 当前的命令行输入的内容位于COMP_WORDS数组中的索引
COMPREPLY 数组 补全列表

接下来我们编写一个补全脚本来测试这些变量, 脚本名字可以随便取, 暂且叫做 test.sh, 文件内容如下:

_complete_test() {
    echo
    echo "COMP_LINE: $COMP_LINE"                # 当前的命令行输入的所有内容(字符串)
    echo "COMP_WORDS: ${COMP_WORDS[@]}"         # 当前的命令行输入的所有内容(数组)
    echo "COMP_CWORD: $COMP_CWORD"              # 数组的索引
    echo "last_word: ${COMP_WORDS[COMP_CWORD]}" # 最后一个输入的单词
    echo "COMPREPLY: $COMPREPLY"                # 补全列表
}

complete -F _complete_test mydocker

我们通过执行source test.sh来使脚本生效, 然后来测试脚本

$ source test.sh

$ mydocker <TAB>
COMP_LINE: mydocker    # 当前的命令行输入的所有内容(字符串)
COMP_WORDS: mydocker   # 当前的命令行输入的所有内容(数组)
COMP_CWORD: 1          # 数组的索引
last_word:             # 最后一个输入的单词
COMPREPLY:             # 补全列表

$ mydocker xy<TAB>
COMP_LINE: mydocker xy    # 当前的命令行输入的所有内容(字符串)
COMP_WORDS: mydocker xy   # 当前的命令行输入的所有内容(数组)
COMP_CWORD: 1             # 数组的索引
last_word: xy             # 最后一个输入的单词
COMPREPLY:                # 补全列表

我们理解了上述的变量之后, 我们是不是可以这样做: 获取当前输入的内容, 如果是mydocker的话, 将补全列表设置为rm rmi stop start; 如果是mydocker rm的话, 查询出所有的容器名, 并将补全列表设置为所有的容器名, startstop同理; 如果是mydocker rmi的话, 补全列表设置为所有的镜像名. 因为每次自动补全都会执行我们的函数, 所以我们的补全列表就是动态的了

在修改test.sh脚本之前, 我们造一点测试数据, 拉取两个镜像并运行这两个镜像

$ docker pull redis
$ docker pull redmine

接下来将test.sh脚本修改为如下内容:

_complete_mydocker() {
    local prev
    prev="${COMP_WORDS[COMP_CWORD-1]}"

    case "${prev}" in
        rm) COMPREPLY=( $(docker ps -a | tail -n +2 | awk '{print $NF}') ) ;;
        rmi) COMPREPLY=( $(docker images | tail -n +2 | awk '{print $1}') );;
        mydocker) COMPREPLY=( rm rmi stop start ) ;;
    esac
}

complete -F _complete_mydocker mydocker

注意: case语句中判断的是倒数第二个输入的单词, 因为当我们运行mydocker r<TAB>时, 最后一个单词是r, 倒数第二个单词是mydocker, 显然此时我们需要的是mydocker的补全列表


修改完脚本后, 要再次执行source test.sh才能使脚本生效. 然后来测试脚本

$ mydocker <TAB>
rm     rmi    start  stop

# 貌似有点问题?
$ mydocker rm<TAB>
rm     rmi    start  stop


$ mydocker rmi <TAB>
redis             redmine

# 貌似又有问题?
$ mydocker rmi redi<TAB>
redis            redmine

目前的补全脚本还是存在一些问题, 其实也很容易发现问题, 无论我们输入mydocker rmi re还是mydocker rmi redi, 都会匹配到补全脚本中的rmi) COMPREPLY=( $(docker images | tail -n +2 | awk '{print $1}') );;, 我们返回的补全列表COMPLETE都是同样的结果, 补全列表并没有变, 补全列表返回的都是redis redmine. 然而, 我们想要的是, 输入mydocker rmi re返回redis redmine, 输入mydocker rmi redi返回redis, 这就需要compgen命令出场了

Tips: 可能有些读者会有疑问, 为什么设置同样的候选列表, 使用-W就和预期一样而使用-F就会出现上述问题, 因为-W已经帮我们实现了类似compgen的功能, 而-F需要我们手动处理才行

5. compgen命令

compgen也是一个Bash内置命令, 其选项几乎和complete是通用的, 其作用就是筛选, 看几个例子大家就明白怎么用了

# -W指定补全列表, 并返回与st相匹配的值
$ compgen -W 'rm    rmi    start    stop' -- st
start
stop

# -W指定补全列表, 并返回与sto相匹配的值
$ compgen -W 'rm    rmi    start    stop' -- sto
stop

# -b指定补全列表为Bash内置命令, 并返回与c相匹配的值
$ compgen -b -- c
caller
cd
command
compgen
complete
compopt
continue

学会了compgen命令, 我们再来修改脚本, 将COMPREPLY=( rm rmi stop start )修改为COMPREPLY=( $(compgen -W "rm rmi stop start" -- 最后一个单词) )就可以动态修改补全列表了

最后将脚本修改如下:

_complete_mydocker() {
    local cur prev mydocker_opts images contains
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    mydocker_opts="rm rmi stop start"
    images=$(docker images | tail -n +2 | awk '{print $1}')
    contains=$(docker ps -a | tail -n +2 | awk '{print $NF}')

    case "${prev}" in
        rm) COMPREPLY=( $(compgen -W "${contains}" -- ${cur}) ) ;;
        rmi) COMPREPLY=( $(compgen -W "${images}" -- ${cur}) );;
        mydocker) COMPREPLY=( $(compgen -W "${mydocker_opts}" -- ${cur}) ) ;;
    esac
}

complete -F _complete_mydocker mydocker

执行脚本后再次测试脚本, 已经能达到我们想要的效果了

$ mydocker <TAB>
rm     rmi    start  stop

$ mydocker rm<TAB>
rm   rmi 

$ mydocker rmi <TAB>
redis             redmine

$ mydocker rmi re<TAB>
redis             redmine

# 这里就会自动补全了
$ mydocker rmi redi<TAB>

6. 别名的自动补全

笔者用docker相关的命令用的比较多, 不想每次敲这么长, 所以直接执行alias d=dockerd设置为docker的别名, 设置后方是方便了很多, 但是用不了自动补全

没关系, 既然docker有自动补全, 那么d也必须有自动补全. 通过执行complete命令发现, docker的补全规则是_docker函数提供的

$ complete -p docker
complete -F _docker docker

那我们只需要执行complete -F _docker d, 将d的补全规则设置为_docker, 这样d也可使用自动补全了

$ d <TAB>
build      cp         events     help       images     inspect    login      network    plugin     pull       restart    run        secret     start      swarm      top        version    
commit     create     exec       history    import     kill       logout     node       port       push       rm         save       service    stats      system     unpause    volume     
container  diff       export     image      info       load       logs       pause      ps         rename     rmi        search     stack      stop       tag        update     wait   

7. 补全规则永久生效

上述例子中, 我们执行补全规则脚本, 使用的是. completion_script或者source completion_script的形式来执行, 而不是通过./completion_scriptbash completion_script的形式来执行, 是因为: 前者的作用范围是当前shell; 而后者会在子shell中执行, 不会影响到当前shell, 看起来就和没执行一样. 子shell是另外一个很重要的概念, 感兴趣的读者可自行了解.

由于source completion_script的作用范围是当前shell, 所以我们设置的补全规则不会影响到其他用户, 同时也会在重新登录后失效. 要使补全规则永久生效, 我们将source completion_script本添加到 ~/.bashrc 或者 ~/.profile 文件中即可. 因为这两个文件是Bash的初始化文件, 每次登录Bash都会执行初始化文件, 所以就可以达到永久生效的效果.

8. 自动加载

最后提一下自动补全脚本是如何自动加载的. 入口是 /etc/bash.bashrc 这个文件, 其会调用 /usr/share/bash-completion/bash_completion/etc/bash_completion

$ cat /etc/bash.bashrc
......
......
if ! shopt -oq posix; then
  if [ -f /usr/share/bash-completion/bash_completion ]; then
    . /usr/share/bash-completion/bash_completion
  elif [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
  fi
fi

查看 /etc/bash_completion 得知, 无论调用哪个文件, 最后实际上调用的都是 /usr/share/bash-completion/bash_completion

$ cat /etc/bash_completion
. /usr/share/bash-completion/bash_completion

打开 /usr/share/bash-completion/bash_completion 文件, 在2151行左右, 有以下一段代码, 大概意思就是会执行 /etc/bash_completion.d 中的每个文件, 所以, 我们将自动补全脚本放在这个路径下, 并设置好读权限, 每次登录系统就会自动加载, 也可以达到永久生效的效果.

$ cat /usr/share/bash-completion/bash_completion
......
......
compat_dir=${BASH_COMPLETION_COMPAT_DIR:-/etc/bash_completion.d}
if [[ -d $compat_dir && -r $compat_dir && -x $compat_dir ]]; then
    for i in "$compat_dir"/*; do
        [[ ${i##*/} != @($_backup_glob|Makefile*|$_blacklist_glob) \
            && -f $i && -r $i ]] && . "$i"
    done
fi

实际上, Ubuntu中一般的自动补全脚本一般放在 /usr/share/bash-completion/completions/, 也会自动加载, 入口是 /etc/bash_completion.d 的2132行左右写道了complete -D -F _completion_loader, 这里就不展开讲了.文章来源地址https://www.toymoban.com/news/detail-498676.html

9. 参考

  1. https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion-Builtins

到了这里,关于在命令行按下tab键之后, 发生了生么?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 浏览器输入一个URL之后发生了什么?

    URL解析 DNS解析 TCP连接 TSL连接 HTTP请求 TCP挥手 接收并解析响应 主要分为: 协议,eg http,https 域名或者ip地址,eg www.baidu.com 域名相对于ip地址来说,更方便人们记忆,但是实际的网络传输中使用的是ip地址 端口号,不同的协议对应不同的端口号,一般可以不写,eg http是80,h

    2024年02月11日
    浏览(73)
  • 在浏览器中输入地址之后发生了什么过程?

    当我们在浏览器地址栏中输入某个网址之后,虽然页面内容几乎瞬间就显示出来,但其中经历了很多个步骤。 1.域名解析 网络上各个计算机之间相互通信均是以其IP地址来识别的,而我们输入的网址仅仅是方便我们记忆而取的别名,计算机不能直接识别,所以就需要将输入的

    2024年02月12日
    浏览(41)
  • Mysql小知识 delete 清空表之后,磁盘空间未发生变化?

    1.1 案例展示 首先我们先来看这样一个例子。 我现在有一个名为 sakila 的数据库,该库中有一个 film 表,这个表中有 1000 条记录,我么先来看下这 1000 条记录占用了多少存储空间: 小伙伴们可以看到,这个文件大小是 360448 个字节。 我们现在执行 delete 命令将这个表清空: 然

    2024年02月07日
    浏览(36)
  • 前端React篇之React setState 调用的原理、React setState 调用之后发生了什么?是同步还是异步?

    在React中, setState 方法是用于更新组件状态的重要方法。当 setState 被调用时,React会对组件进行重新渲染,以反映状态的变化。 具体的执行过程如下: 调用 setState 入口函数 :当你在组件中调用 setState 方法时,实际上是调用了React组件的 setState 方法。这个方法在内部充当一

    2024年04月17日
    浏览(51)
  • 进入mysql命令行之后,怎么退出

    展开全部 进入mysql命令行之后可以按键ctrl+c来退出mysql命令客户端; 也可以在命令行输入quit按回车键来62616964757a686964616fe4b893e5b19e31333431366338退出命令行。 扩展资料 mysql命令行常用命令使用方法介绍: 1、连接和断开服务器 shell mysql -h host -u user -p Enter password: ******** host和user分

    2023年04月09日
    浏览(49)
  • 当你打开终端并输入命令时会发生什么?(下)

    哈喽大家好,我是咸鱼 我们先来大致回顾一下文章《当你打开终端并输入命令时会发生什么?(上)》的内容 终端设备是由电传打字机演变过来的,电传打字机通过物理线与大型计算机连接在一块来实现输入输出 如上图,分别是二战时期的电传打字机和西门子 “Fernscheiber

    2024年02月04日
    浏览(37)
  • 当你打开终端并输入命令时会发生什么?(上)

    哈喽大家好,我是咸鱼 参加过校招面试的小伙伴们肯定对下面这道面试题很熟悉:“当你在浏览器输入一段网址后会发生什么?”。这道面试题可以说是很经典了,因为其涉及大量网络协议,可以非常直观的看出小伙伴们对计算机网络体系的整体把握程度 但如果问题换成:

    2024年02月05日
    浏览(30)
  • 矩阵行列式的按行按列展开复习

    1,行列式按某一行(列)展开 例如: 按元素5展开 则去掉所在行,所在列得到, 这样5的变成由3阶变成2阶行列式 5的行列式比较好算 这个叫做的余子式 称为 它的代数余子式为  ,代数余子式与余子式区别是前面多一个符号是(-1)该行该列之和 D= 按第二行展开    +  +   = 24 - 60 + 36

    2024年02月11日
    浏览(67)
  • Linux之history、tab、alias、命令执行顺序、管道符以及exit

    目录 Linux之history、tab、alias、命令执行顺序、管道符以及exit history历史命令 格式 参数 修改默认记录历史命令条数  案例         案例1 --- 显示history历史记录中出现次数最高的top10         案例2 --- 增加history显示的时间信息 命令与文件名补全 --- tab 命令别名  格式 案

    2024年02月09日
    浏览(63)
  • systemctl start jenkins执行命令之后报错

    报错阐述 在安装好jenkins和修改了jenkins配置文件之后,重启jenkins时报错,报错内容如下: 执行了 systemctl start jenkins 命令之后,出现的报错信息如下所示: 原因解析: 我报错的原因是jenkins的版本和java的版本不匹配导致的,更换了jenkins版本之后就可以了。

    2024年02月16日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包