【環(huán)球報資訊】在命令行按下tab鍵之后, 發(fā)生了生么?
時間:2023-06-25 06:26:08
當我們輸入ls 再按下TAB時, 會自動列出當前路徑下所有的文件;
當我們輸入ls a再按下TAB時, 會自動列出當前路徑下所有以a開頭的文件; 若只有一個以a開頭的文件, 將會自動補全;
當我們輸入type 再按下TAB時, 會自動列出全所有可執(zhí)行的命令;
(相關資料圖)
當我們輸入docker rmi 再按下TAB時, 會自動列出所有鏡像名;
一個顯示文件, 一個顯示命令, 一個顯示容器名, 這是怎么做到的?
本文將帶你一探究竟, 并以docker為例, 實現(xiàn)一個簡單的docker自動補全規(guī)則
2. complete命令上述功能, 是 Bash 2.05 版本新增的功能, 叫做自動補全. 自動補全允許我們對命令和選項設置補全規(guī)則, 按下TAB之后, 會根據(jù)我們設置的規(guī)則返回補全列表, 當補全列表只有一個元素時, 就會自動補全.
bash自動補全用到最主要的命令就是complete, 這是一個Bash的內(nèi)置命令(builtin), 用于指定某個命令的補全規(guī)則, 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 使用預設的補全規(guī)則, 可使用的補全規(guī)則如下: alias 補全列表設置為所有已定義的別名. 等同于-a builtin 補全列表設置為所有shell內(nèi)置命令. 等同于-b command 補全列表設置為所有可執(zhí)行命令. 等同于-c directory 補全列表設置為當前路徑下所有目錄. 等同于-d, 也就是說 complete -d xxx 與 complete -A directory xxx 等價, 只是寫法不一樣 export 補全列表設置為所有環(huán)境變量名. 等同于-e file 補全列表設置為當前路徑下所有文件. 等同于-f function 補全列表設置為所有函數(shù)名 signal 補全列表設置為所有信號名 user 補全列表設置為所有用戶名. 等同于-u variable 補全列表設置為所有變量名. 等同于-v -F function 用函數(shù)來定義補全規(guī)則, 函數(shù)運行后 COMPREPLY 變量做為補全列表 -W wordlist 用一個字符串來做為補全列表 -p name 顯示某個命令的補全規(guī)則, 如果 name 為空的話則顯示所有命令的補全規(guī)則 -r 移除某個命令的補全規(guī)則ls命令默認的補全列表是當前路徑下所有文件, 現(xiàn)在, 我們改變其補全規(guī)則, 讓其補全列表變?yōu)樗锌蓤?zhí)行命令
$ cd /# 先測試下 ls 默認的補全規(guī)則$ lsbin/ boot/ dev/ etc/ home/ lib/ lib32/ lib64/ libx32/ media/ mnt/ opt/ proc/ root/ run/ sbin/ srv/ sys/ tmp/ usr/ var/# 修改 ls 的補全規(guī)則, 讓所有可執(zhí)行命令作為其補全列表$ complete -c ls# 測試修改補全規(guī)則后的 ls$ ls whowho whoami whoopsie whoopsie-preferences 提示: 上述改變的補全規(guī)則只在當前shell有效, 即不會影響到其他用戶, 重新登錄后也會失效. 所以想要恢復ls命令的補全規(guī)則的話, 只需要退出再重新登錄服務器就好了. 至于如何永久改變補全規(guī)則, 請看后文.
我們再來看下type命令預設的補全規(guī)則, 發(fā)現(xiàn)type命令設置的補全列表是所有可執(zhí)行命令
$ complete -p typecomplete -c type至此, 我們應該知道引言中所提出的問題, 為什么ls命令會文件而type命令會列出命令
盡管Bash預設了很多補全規(guī)則, 但是很明顯, 如果我們自己想給docker命令寫補全規(guī)則的話, 預設的補全規(guī)則顯然是不能滿足我們需求的. 所以, 我們可以用-W選項來自定義補全列表.
假設我們自己寫了個mydocker命令, 可以使用的功能有mydocker rm, mydocker rmi, mydocker stop, mydocker start, 顯然, mydocker的補全列表為rm rmi stop start, 我們可以使用下面的命令來設置補全規(guī)則
# 將 rm rmi stop start 設置為 mydocker 的補全列表$ complete -W "rm rmi stop start" mydocker$ mydockerrm rmi start stop$ mydocker ststart stop 到這一步, 我們已經(jīng)能給相當一部分的命令來定義補全規(guī)則了. 但是, 上述的"-W"選項, 是靜態(tài)的補全規(guī)則, 不會隨著某些條件的改變而變化; docker rmi 所有顯示的鏡像名, 會隨著鏡像的增刪而改變; docker rm 所有顯示的容器名, 會隨著容器的增刪而改變; 是動態(tài)的補全規(guī)則, 這是如何做到的呢?
我們直接通過-p選項來查看docker預設的補全規(guī)則就好了, 發(fā)現(xiàn)docker命令是通過-F _docker來指定補全規(guī)則; 再通過type _docker來查看_docker是什么玩意, 發(fā)現(xiàn)_docker是一個非常復雜的函數(shù)
$ complete -p dockercomplete -F _docker docker$ type _docker_docker is a function_docker (){ ......}接下來, 我們來好好聊一聊-F這個選項
-F選項會指定一個函數(shù)做為補全規(guī)則, 每次按下TAB時, 就會調(diào)用這個函數(shù), 并且將COMPREPLY的值做為補全列表, 所以我們需要在函數(shù)中處理COMPREPLY變量
除了COMPREPLY變量外, Bash還提供了一些變量來方便我們獲取當前的輸入
| 變量名 | 類型 | 說明 |
|---|---|---|
| COMP_LINE | 字符串 | 當前的命令行輸入的所有內(nèi)容 |
| COMP_WORDS | 數(shù)組 | 當前的命令行輸入的所有內(nèi)容, 和COMP_LINE不同的是, 這個變量是一個數(shù)組 |
| COMP_CWORD | 整數(shù) | 當前的命令行輸入的內(nèi)容位于COMP_WORDS數(shù)組中的索引 |
| COMPREPLY | 數(shù)組 | 補全列表 |
接下來我們編寫一個補全腳本來測試這些變量, 腳本名字可以隨便取, 暫且叫做 test.sh, 文件內(nèi)容如下:
_complete_test() { echo echo "COMP_LINE: $COMP_LINE" # 當前的命令行輸入的所有內(nèi)容(字符串) echo "COMP_WORDS: ${COMP_WORDS[@]}" # 當前的命令行輸入的所有內(nèi)容(數(shù)組) echo "COMP_CWORD: $COMP_CWORD" # 數(shù)組的索引 echo "last_word: ${COMP_WORDS[COMP_CWORD]}" # 最后一個輸入的單詞 echo "COMPREPLY: $COMPREPLY" # 補全列表}complete -F _complete_test mydocker我們通過執(zhí)行source test.sh來使腳本生效, 然后來測試腳本
$ source test.sh$ mydocker COMP_LINE: mydocker # 當前的命令行輸入的所有內(nèi)容(字符串)COMP_WORDS: mydocker # 當前的命令行輸入的所有內(nèi)容(數(shù)組)COMP_CWORD: 1 # 數(shù)組的索引last_word: # 最后一個輸入的單詞COMPREPLY: # 補全列表$ mydocker xyCOMP_LINE: mydocker xy # 當前的命令行輸入的所有內(nèi)容(字符串)COMP_WORDS: mydocker xy # 當前的命令行輸入的所有內(nèi)容(數(shù)組)COMP_CWORD: 1 # 數(shù)組的索引last_word: xy # 最后一個輸入的單詞COMPREPLY: # 補全列表 我們理解了上述的變量之后, 我們是不是可以這樣做: 獲取當前輸入的內(nèi)容, 如果是mydocker的話, 將補全列表設置為rm rmi stop start; 如果是mydocker rm的話, 查詢出所有的容器名, 并將補全列表設置為所有的容器名, start和stop同理; 如果是mydocker rmi的話, 補全列表設置為所有的鏡像名. 因為每次自動補全都會執(zhí)行我們的函數(shù), 所以我們的補全列表就是動態(tài)的了
在修改test.sh腳本之前, 我們造一點測試數(shù)據(jù), 拉取兩個鏡像并運行這兩個鏡像
$ docker pull redis$ docker pull redmine接下來將test.sh腳本修改為如下內(nèi)容:
_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語句中判斷的是倒數(shù)第二個輸入的單詞, 因為當我們運行mydocker r時, 最后一個單詞是r, 倒數(shù)第二個單詞是mydocker, 顯然此時我們需要的是mydocker的補全列表
修改完腳本后, 要再次執(zhí)行source test.sh才能使腳本生效. 然后來測試腳本
$ mydocker rm rmi start stop# 貌似有點問題?$ mydocker rmrm rmi start stop$ mydocker rmi redis redmine# 貌似又有問題?$ mydocker rmi rediredis redmine 目前的補全腳本還是存在一些問題, 其實也很容易發(fā)現(xiàn)問題, 無論我們輸入mydocker rmi re還是mydocker rmi redi, 都會匹配到補全腳本中的rmi) COMPREPLY=( $(docker images | tail -n +2 | awk "{print $1}") );;, 我們返回的補全列表COMPLETE都是同樣的結(jié)果, 補全列表并沒有變, 補全列表返回的都是redis redmine. 然而, 我們想要的是, 輸入mydocker rmi re返回redis redmine, 輸入mydocker rmi redi返回redis, 這就需要compgen命令出場了
Tips: 可能有些讀者會有疑問, 為什么設置同樣的候選列表, 使用-W就和預期一樣而使用-F就會出現(xiàn)上述問題, 因為-W已經(jīng)幫我們實現(xiàn)了類似compgen的功能, 而-F需要我們手動處理才行
compgen也是一個Bash內(nèi)置命令, 其選項幾乎和complete是通用的, 其作用就是篩選, 看幾個例子大家就明白怎么用了
# -W指定補全列表, 并返回與st相匹配的值$ compgen -W "rm rmi start stop" -- ststartstop# -W指定補全列表, 并返回與sto相匹配的值$ compgen -W "rm rmi start stop" -- stostop# -b指定補全列表為Bash內(nèi)置命令, 并返回與c相匹配的值$ compgen -b -- ccallercdcommandcompgencompletecompoptcontinue學會了compgen命令, 我們再來修改腳本, 將COMPREPLY=( rm rmi stop start )修改為COMPREPLY=( $(compgen -W "rm rmi stop start" -- 最后一個單詞) )就可以動態(tài)修改補全列表了
最后將腳本修改如下:
_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執(zhí)行腳本后再次測試腳本, 已經(jīng)能達到我們想要的效果了
$ mydocker rm rmi start stop$ mydocker rmrm rmi $ mydocker rmi redis redmine$ mydocker rmi reredis redmine# 這里就會自動補全了$ mydocker rmi redi 6. 別名的自動補全筆者用docker相關的命令用的比較多, 不想每次敲這么長, 所以直接執(zhí)行alias d=docker把d設置為docker的別名, 設置后方是方便了很多, 但是用不了自動補全
沒關系, 既然docker有自動補全, 那么d也必須有自動補全. 通過執(zhí)行complete命令發(fā)現(xiàn), docker的補全規(guī)則是_docker函數(shù)提供的
$ complete -p dockercomplete -F _docker docker那我們只需要執(zhí)行complete -F _docker d, 將d的補全規(guī)則設置為_docker, 這樣d也可使用自動補全了
$ d 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. 補全規(guī)則永久生效上述例子中, 我們執(zhí)行補全規(guī)則腳本, 使用的是. completion_script或者source completion_script的形式來執(zhí)行, 而不是通過./completion_script或bash completion_script的形式來執(zhí)行, 是因為: 前者的作用范圍是當前shell; 而后者會在子shell中執(zhí)行, 不會影響到當前shell, 看起來就和沒執(zhí)行一樣. 子shell是另外一個很重要的概念, 感興趣的讀者可自行了解.
由于source completion_script的作用范圍是當前shell, 所以我們設置的補全規(guī)則不會影響到其他用戶, 同時也會在重新登錄后失效. 要使補全規(guī)則永久生效, 我們將source completion_script本添加到 ~/.bashrc或者 ~/.profile文件中即可. 因為這兩個文件是Bash的初始化文件, 每次登錄Bash都會執(zhí)行初始化文件, 所以就可以達到永久生效的效果.
最后提一下自動補全腳本是如何自動加載的. 入口是 /etc/bash.bashrc這個文件, 其會調(diào)用 /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 fifi查看 /etc/bash_completion得知, 無論調(diào)用哪個文件, 最后實際上調(diào)用的都是 /usr/share/bash-completion/bash_completion
$ cat /etc/bash_completion. /usr/share/bash-completion/bash_completion打開 /usr/share/bash-completion/bash_completion文件, 在2151行左右, 有以下一段代碼, 大概意思就是會執(zhí)行 /etc/bash_completion.d中的每個文件, 所以, 我們將自動補全腳本放在這個路徑下, 并設置好讀權限, 每次登錄系統(tǒng)就會自動加載, 也可以達到永久生效的效果.
$ 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" donefi實際上, Ubuntu中一般的自動補全腳本一般放在 /usr/share/bash-completion/completions/, 也會自動加載, 入口是 /etc/bash_completion.d的2132行左右寫道了complete -D -F _completion_loader, 這里就不展開講了.
相關稿件
【環(huán)球報資訊】在命令行按下tab鍵之后, 發(fā)生了生么?
谷歌稱愿為Stadia花五年打造3A游戲 奈何成本太高_當前速讀
如何養(yǎng)護丙烯酸涂料,讓你的禮物更持久美麗?男生必看送女生禮物小技巧!|今亮點
環(huán)球今頭條!重慶四環(huán)來了!將形成“四環(huán)二十二射六十聯(lián)線”高速公路網(wǎng)布局
Lisa Selesner(lisa selesner)|環(huán)球關注
天天新動態(tài):女子用蹲便器洗粽葉被吐槽 店老板:沒連接下水道
上汽大眾全新Polo Plus怎么樣及廣汽謳歌TLX-L 2.4L多少錢|全球速訊
【當前獨家】讓傳統(tǒng)節(jié)日綻放時代新韻(今日談)
潮訊:蘋果終于修復這漏洞;安卓比iOS更容易使用;手機NFC功能要徹底變了;Flyme10修復了這些問題
南方 16 條河流發(fā)生超警洪水,水利部門全力做好暴雨洪水防御 天天微資訊
山東高速駛?cè)敫哔|(zhì)量發(fā)展“快車道” 當前視訊
西安經(jīng)開區(qū)舉辦西安市“新征程、再出發(fā)”應急詩歌誦讀暨安全文化演出活動-全球滾動
老人發(fā)病將孫子托付路邊店主后病逝 疑似是心肌梗塞|今日關注
【世界時快訊】頂級影像旗艦——vivoX90Pro+,人手僅需5878元


