- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
当我们输入 ls 再按下TAB时, 会自动列出当前路径下 所有的文件 ,
当我们输入 ls a 再按下TAB时, 会自动列出当前路径下 所有以a开头的文件 ; 若只有 一个 以a开头的文件, 将会 自动补全 ,
当我们输入 type 再按下TAB时, 会自动列出全 所有可执行的命令 ,
当我们输入 docker rmi 再按下TAB时, 会自动列出所有 镜像名 ,
一个显示文件, 一个显示命令, 一个显示容器名, 这是怎么做到的?
本文将带你一探究竟, 并以docker为例, 实现一个简单的docker自动补全规则 。
上述功能, 是 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 命令会列出命令 。
尽管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
到这一步, 我们已经能给相当一部分的命令来定义补全规则了. 但是, 上述的'-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 这个选项 。
-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 的话, 查询出所有的容器名, 并将补全列表设置为所有的容器名, start 和 stop 同理; 如果是 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 需要我们手动处理才行 。
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>
笔者用docker相关的命令用的比较多, 不想每次敲这么长, 所以直接执行 alias d=docker 把 d 设置为 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
上述例子中, 我们执行补全规则脚本, 使用的是 . completion_script 或者 source completion_script 的形式来执行, 而不是通过 ./completion_script 或 bash completion_script 的形式来执行, 是因为: 前者的作用范围是当前shell; 而后者会在子shell中执行, 不会影响到当前shell, 看起来就和没执行一样. 子shell是另外一个很重要的概念, 感兴趣的读者可自行了解. 。
由于 source completion_script 的作用范围是当前shell, 所以我们设置的补全规则不会影响到其他用户, 同时也会在重新登录后失效. 要使补全规则永久生效, 我们将 source completion_script 本添加到 ~/.bashrc 或者 ~/.profile 文件中即可. 因为这两个文件是Bash的初始化文件, 每次登录Bash都会执行初始化文件, 所以就可以达到永久生效的效果. 。
最后提一下自动补全脚本是如何自动加载的. 入口是 /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 , 这里就不展开讲了. 。
最后此篇关于在命令行按下tab键之后,发生了生么?的文章就讲到这里了,如果你想了解更多关于在命令行按下tab键之后,发生了生么?的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我在看 Tabs UI 的选项。默认行为是它们水平堆叠。有没有办法改变它? 我想堆叠Tabs垂直。它将节省大量的 UI“不动产”,尤其是在移动应用程序 UI 中。 最佳答案 尝试这个: Tabs ta
问题:在 Zsh 中使用 TAB 向后导航,类似于在 Firefox 中 Shift-TAB Shift-TAB 应该执行的操作示例 我在终端中运行以下代码 ls 我明白了 A B C D E F
有很多人想知道如何完成制表符。这不是这些问题之一。问题是如何将 tab 键分配给 tab 补全? alt-tab/esc-tab 很痛苦。在面板禁用模式选项卡完成工作,这让我暂时...但我仍然希望面板
所以我有一个带有 3 个屏幕的 TabNavigator。 import React from 'react'; import {TabNavigator,createBottomTabNavigat
我想触发一个事件,例如当我在文本框中按 Tab 键时显示警报消息。 $("input").blur(function (e) { if (e.which == 9) alert(
我将这段代码设置为创建一个选项卡式菜单,它显示为一个选项卡式菜单,但当我加载网站时,选项卡在单击时不会改变。在“检查元素”中它说 $( "#tabs").tabs();不是函数。我不知道下一步要解决这
表单中有多个输入字段的示例,因为有一些字段在此之前会自动填充,如果我按 Tab 键,那么它应该跳过该自动填充的输入字段并应转到空白输入字段 我尝试过,但是当我开始在空白输入中写入时会发生什么,它也会自
我想写一个小的 chrome 扩展程序,它应该从网页 A(当前网页)获取信息,将选项卡更新到网页 B,然后将代码注入(inject)网页 B。不幸的是,以下代码正在将网页更新到 B 但注入(injec
如果当前事件选项卡中的表单很脏,我试图阻止 mat-tab 的选项卡更改。 但是我找不到拦截选项卡更改事件的方法。 // Tab 0 Content // Tab
我已从 MacOS Mojave 上的默认终端切换到 iterm2 .我有一个关于从当前选项卡打开新选项卡的问题。 确实,我希望与上一个当前选项卡处于同一路径。 为此,我执行了经典程序,即转到 ite
我在我的网站的两页上有此代码,但在一页上该功能不起作用。 Firebug 向我显示“$(...).tabs 不是函数”。我不明白为什么,谁能告诉我什么是错的? 这是有效的: http://www.in
我需要可靠的方法来为 Windows 和 XP 创建 %tab% (包含一个制表符)。 SET TAB=" " 应该适用于 Windows 7(未测试)但不适用于 Win XP(已测试)。 这个 fo
我正在尝试使用 RMarkdown 制作静态网页。我想定义一个 UI,它有第一层选项卡,然后是第一层下面的选项卡。我已经在 RMarkdown: Tabbed and Untabbed heading
我正在尝试使用 RMarkdown 制作静态网页。我想定义一个 UI,它有第一层选项卡,然后是第一层下面的选项卡。我已经在 RMarkdown: Tabbed and Untabbed heading
我在使用 chrome.tabs.create 方法打开多个选项卡时遇到问题。我正在尝试使用 chrome.tabs.create 循环打开大约 9 个选项卡,但打开的选项卡数量仅限于 4 个。看起来
我正在使用 Material ui 的标签,并且能够对标签的指示器进行更改。但是,我正在尝试使用样式将每个选项卡的指示器宽度减小到某个固定宽度。但似乎指标以一些计算值定位在左侧,并且给它一个宽度不会使
我在 OS X 终端中使用 emacs 24.3,并且遇到了一些奇怪的事情。 在 markdown-mode.el 中,tab 键通过 (define-key map (kbd "") 'markdo
在 Chrome 开发者工具上 Uncaught (in promise) Error: There is no clipping info for given tab at E._handleRes
在vim中,我想将:tabnew缩短为:tn,将:tabp缩短为:th,将:tabn缩短为:tl在我的.vimrc中。知道我将如何重新映射这样的命令吗? 最佳答案 使用cabbrev: ca tn t
我读过几个主题,讨论 IE 中的地址栏在使用 TAB 时基本上是第一个获得焦点的主题(MSDN 自己的文档讨论了这一点)。 然而,我也见过一些情况,但情况并不总是如此...... 我有一个母版页,内容
我是一名优秀的程序员,十分优秀!