簡介
什麼是補全, 什麼是補全規則;
補全規則怎麼選;
補全規則怎麼生成候選項;
compgen, complete, compopt
指令;案例
項目路徑
- https://github.com/scop/bash-completion/
本文描述的內容可能因爲
bash
版本差異, 執行會有細微差異;
什麼是補全
shell
交互式命令行<tab>
鍵就會觸發補全;補全一般是補全參數; 當然也有補全指令; 補全可以避免使用者記憶, 減少拼寫錯誤;
補全規則
補全規則:
compspec: completion specification
;即爲某個指令生成參數補全集合的一些腳本指令,
bash
內部的一些變量,bash
可以提供的很多信息;生成的是一系列的單詞組; 供候選補齊;
補全規則選擇
空指令: 使用
complete -E
定義的規則, 用其生成候選集合; 一般可以配置爲自己常用的一些指令; 比如編譯環境下生成編譯指令集合;相對或絕對路徑: 查找和路徑完全匹配的規則; 無匹配則用其最後文件名作爲指令進行規則搜索;
指令: 普通指令則同樣進行匹配搜索;
無指令匹配: 採用
complete -D
定義的默認規則生成;無默認規則: 指令是否爲
alias
, 如果是恢復原狀, 使用恢復後的指令搜索;(部分版本支持)
無任何匹配規則: 採用
Bash
的默認補全機制;
補全規則生成候選項
-A action
或其簡寫-abcdefgjksuv
生成集合;生成集合: 根據拼寫一截的補全; 比如
git sta
就有補全stage stash status
;根據sta
過濾;-f -d
生成集合會被FIGNORE
過濾;
-G
根據文件擴張規則生成集合比如
-G "*.sh"
, 這個就是文件擴張生成當前目錄下sh
結尾的文件; 不會被sta
這種影響, 會全部納入; 但是受到FIGNORE
的影響;
-W
字符串被${IFS}
分詞後的集合
-W "hello world $PATH"
, 進行bash
的擴張後生成的字符串, 再進行bash
分詞規則生成字符串集合;git sta
會從過濾集合中過濾前綴sta
的集合;-W
某些場景完全可以替代; 差距就是是否需要對集合過濾;
-F -C
: 會生成變量;COMP_LINE, COMP_POINT, COMP_KEY, COMP_TYPE
;-F
還會生成COMP_WORDS, COMP_CWORD
; 入參:$1
指令名,$2
補全名, 如sta
;$3
不全名前一個單詞, 比如git sta
,sta
前一個就是git
; 無過濾規則; 即生成的所有都納入候選, 需指令提供者或函數提供者根據輸入參數進行過濾;
-F
優先: 是shell
函數; 可以通過compgen -A function
查看當前環境下的函數定義; 當然一般是局部自定義的;-F
指定的function
一般結合compgen, comopt
使用; 返回集合通過COMREPLY
數組, 即一般使用COMPREPLY=( hello world )
;-C
次之: 基本和-F
相同, 除了沒有COMP_WORDS, COMP_CWORD
; 輸出到標準輸出的字符集按\n
進行分詞, 生成結果集; 可以結合\
使用;
-X
: 過濾文件擴張集合, 不會過濾-F, -C
生成的集合;過濾規則:
&
被替代爲sta
; 可以用\&
保持願意;!
則是取反;shopt nocasematch
可以按照不區分大小寫匹配;complete -G "*" -X "!&*" todo
; 完全匹配的會被刪除; 保留不匹配的;
-P -S
添加前後綴;補充
-o
選項;
- 如果前面沒有任何匹配集合, 且規則指定了
-o dirnames
; 會將當前目錄下的文件夾名納入匹配, 並過濾; 但是如果有匹配集合這個選項就不會生效;- 指定了
-o plusdirs
; 會將目錄納入集合; 和-o dirnames
區別在於,-o plusdirs
一定會生效;complete -W "hello" -o plusdirs todo
;如果上面的
complete
指定的規則;bash completion,Readline
的補齊規則會被禁用; 可以通過-o bashdefault
開啓bash complettion
,-o default
開啓Readline completion
作爲補充手段, 這個是沒有任何生成集合的時候纔會生效, 有就不會生效;如果有
compspec
是目錄, 會補全/
; 可以通過一些手段規避;complete -W "hello $(find . -maxdepth 1 -mindepth 1 -type d -printf "%f\n")" todo
;compgen -d
生成的也沒有後綴;補充: 動態加載規則
- 一般都是一次性的加載; 但是當我們
-F
的返回值是124
的時候, 會重新加載當前指令的complete
; 即返回124
表示當前指令的規則發生了變化;_completion_loader() { . "/etc/bash_completion.d/$1.sh" >/dev/null 2>&1 && return 124 } complete -D -F _completion_loader -o bashdefault -o default
- 默認規則, 用於動態生成某個指令的規則; 即使用了指令並觸發才生成;
指令: compgen, complete, compopt
compgen
格式:
compgen [option] [word]
- 這個是手動觸發; 即模擬
complete
;option
是可以是complete
的所有選項, 除了-r, -p
;-F -C
在這裏也不太好用; 因爲相關參數沒有設置;word
用於過濾; 即git sta
案例; 可以理解爲sta
就是這裏的word
參與過濾; 沒有就不過濾; 返回所有;- 具體使用參見後面案例
compopt
格式:
compopt [-o option] [-DEI] [+o option] [name]
, 即控制某個指令的選項開關; 不修改其他規則;
-o option
: 打開某個選項;+o option
: 關閉某個選項;name
: 指定具體指令, 未指定表示當前指令;[-DEI]
: 指定是name
忽略;
complete
指令原型;
complete [-abcdefgjksuv] [-o comp-option] [-DEI] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] name [name ...] complete -pr [-DEI] [name ...]
簡介
-abcdefgjksuv
是-A action
的簡寫, 後面介紹;-o comp-option
: 補充手段開關;-DEI
默認規則或空指令規則;-A action
:bash
提供生成集合;-G
自定義文件擴張生成集合;-W "hello world $(ls) $((1+1)) ${var}"
: 按照${IFS}
分詞生成自定義集合;-F function
:shell
腳本生成集合;-C
指令生成集合;-X
文件擴張過濾器;-P -S
前後綴;-pr
輸出或刪除;
complete -pr [-DEI] [name ...]
-p
: 輸出name ...
對應的規則; 未指定name
則是輸出所有;
-r
: 刪除name ...
對應的規則; 未指定name
則是刪除所有;
-pr [-DEI]
: 刪除默認, 空白指令規則,name
會忽略;
-D
: 默認選項, 即某指令沒有規則, 則使用默認規則; 爲所有沒有規則的指令提供的規則;
-E
: 空規則; 即沒有指定指令的時候的規則; 即空白行;
-I
: 部分不支持; 即指令補齊規則;; |
也會觸發;
-DEI
同時指定;-D > -E > -I
; 優先級小的會被忽略;
-o comp-option
-o bashdefault
: 有規則默認關閉; 開啓後規則未生成任何候選項時才觸發bash
補齊規則;
-o default
: 有規則默認關閉; 開啓後規則未生成任何候選項時才觸發Readline
補齊規則;
-o dirnames
: 開啓後規則未生成任何候選項時, 提供當前目錄下的文件夾, 會有後綴/
;
-o filenames
: 開啓後規則未生成任何候選項時, 提供當前目錄下的所有文件; 並會進行文件夾加/
, 特殊字符文件或文件夾括起來; 消除末尾空格; 一般和-F
使用;
-o noquote
: 關閉路徑中特殊符號處理; 默認會括起來;
-o nosort
: 結果集不進行排序; 默認排序;
-o nospace
: 補齊後不添加空格; 默認添加空格; 這種一般很少用; 都會添加空格分詞;
-o plusdirs
: 規則有匹配集合時才添加;並規律; 且有後綴/
;
-A action
: 都可以通過compgen -A action
查看生成集合; 一般在函數中使用;
-A alias | -a
: 提供alias
集合到候選項;
-A arrayvar
: 提供bash
中array
類型變量名到候選項;
-A binding
:bash Readline key binding names
添加到候選項;
-A builtin | -b
:bash
內置指令添加到候選項;
-A command | -c
: 將指令添加到候選項;
-A directory | -d
: 將當前路徑名加入候選項;
-A disabled
: 將shell
禁用的內置指令納入候選項; 一般爲空;
-A enabled
: 將shell
啓用的內置指令納入候選項;
-A export | -e
: 將shell export
的變量名納入候選項;
-A file | -f
: 將當前文件夾下的所有文件夾和文件納入候選項;
-A function
: 將當前環境所有可訪問的shell
函數納入候選項;declar -f function_name
查看代碼;shopt -s extdebug;declare -F function_name
查看函數定義的文件位置; https://www.cyberciti.biz/faq/how-to-find-bash-shell-function-source-code-on-linuxunix/
-A group | -g
: 用戶組名納入候選項;
-A helptopic
: 內置指令help
支持的命令;
-A hostname
: 查看當前houstname
, 一般是HOSTFILE
指定的文件;
-A job | -j
:job
控制的所有程序; 死的活的;
-A keyword | -k
:bash
內置的關鍵字, 如if
之類的;
-A running
:job
控制的程序, 活的;
-A service | -s
: 將當前所有服務名納入候選項;
-A setopt
:set
指令可以通過-o
指定的所有合法選項納入候選項;
-A signal
: 將當前系統信號名納入候選項;
-A stopped
:job
控制死的程序;
-A user | -u
: 當前系統所有用戶名納入候選項;
-A variable | -v
: 將當前環境所有shell
變量名納入候選項;
-C command
: 指令
子
shell
中執行; 輸入:$1
:指令名;$2
:git sta
中的sta
;$3
:git sta
中sta
的前一個單詞git
; 輸出按\n
分詞納入候選集合; 集合不會被過濾;
-F function
同
-F
, 輸出通過COMPREPLY
變量指定; 變量是array
類型; 不會被git sta
過v了;
-G globpat
文件擴展, 具體參上; 不會被
git sta
的sta
過濾;
-P prefix | -S suffix
: 集合前都添加前綴, 後綴;
一般是
-P --
, 即自動補齊;
-W wordlist
-W "hello world $(ls) ${var}"
,bash
擴張處理後將結果按照IFS
拆分並過濾;
-X filterpat
專門對文件擴張結果進行過濾; 具體參數;
總結
本文主要講了什麼是規則; 規則怎麼選擇的; 候選項怎麼生成的; 以及指令
compgen
模擬;complete
定義;compopt
動態修改;-F
返回124
重新加載;
案例
官方案例
https://github.com/scop/bash-completion/
文件夾處理
-F -W
生成的路徑無/
,-A,-o
開啓的選項有;
補全空格
function test_func { # echo "$1:$2:$3:${COMP_WORDS[@]}:" >> log.txt local cur=${COMP_WORDS[1]} local param=$2 case ${COMP_WORDS[1]} in sdk2app) if [[ "${param}" == "" ]] ; then COMPREPLY=($(compgen -W "front back" -- $param)) else COMPREPLY=("sdk2app") fi;; *) COMPREPLY=($( compgen -W "sdk2app" $cur));; esac; } complete -F test_func todo
案例三: 空白補全
$ complete -E -W todo $ complete complete -W 'todo' -E $ <tab>
絕對或相對路徑未匹配使用文件名
function _comp { echo $1:$2:$3 >> log.txt COMPREPLY=( aaa bbb ) } complete -F _comp test.sh
/aaa/bbb/test.sh的規則不存在, 使用test.sh的規則.
ch@ch:~/ch/shfile/complete$ source func.sh ch@ch:~/ch/shfile/complete$ complete complete -F _comp test.sh ch@ch:~/ch/shfile/complete$ /aaa/bbb/test.sh aaa bbb ch@ch:~/ch/shfile/complete$ /aaa/bbb/test.sh
-A action
的最先生成, 且會被候選詞過濾;ch@ch:~/ch/shfile/complete$ complete -r ch@ch:~/ch/shfile/complete$ source func.sh ch@ch:~/ch/shfile/complete$ complete complete -A signal todo ch@ch:~/ch/shfile/complete$ todo SIG SIGABRT SIGCONT SIGINT SIGKILL SIGQUIT SIGRTMAX-11 SIGRTMAX-2 SIGRTMAX-6 SIGRTMIN SIGRTMIN+12 SIGRTMIN+2 SIGRTMIN+6 SIGSEGV SIGTERM SIGTTOU SIGVTALRM SIGALRM SIGFPE SIGIO SIGPIPE SIGRTMAX SIGRTMAX-12 SIGRTMAX-3 SIGRTMAX-7 SIGRTMIN+1 SIGRTMIN+13 SIGRTMIN+3 SIGRTMIN+7 SIGSTKFLT SIGTRAP SIGURG SIGWINCH SIGBUS SIGHUP SIGJUNK(32) SIGPROF SIGRTMAX-1 SIGRTMAX-13 SIGRTMAX-4 SIGRTMAX-8 SIGRTMIN+10 SIGRTMIN+14 SIGRTMIN+4 SIGRTMIN+8 SIGSTOP SIGTSTP SIGUSR1 SIGXCPU SIGCHLD SIGILL SIGJUNK(33) SIGPWR SIGRTMAX-10 SIGRTMAX-14 SIGRTMAX-5 SIGRTMAX-9 SIGRTMIN+11 SIGRTMIN+15 SIGRTMIN+5 SIGRTMIN+9 SIGSYS SIGTTIN SIGUSR2 SIGXFSZ ch@ch:~/ch/shfile/complete$ todo SIGA SIGABRT SIGALRM ch@ch:~/ch/shfile/complete$ todo SIGA
指令如上:
complete -A signal todo
-A action
中-A file | -f
,-A directory | -d
會收到變量FIGNORE
的值影響;ch@ch:~/ch/shfile/complete$ complete -r ch@ch:~/ch/shfile/complete$ complete ch@ch:~/ch/shfile/complete$ complete -f todo ch@ch:~/ch/shfile/complete$ ls func.sh log.txt test.txt ch@ch:~/ch/shfile/complete$ todo func.sh log.txt test.txt ch@ch:~/ch/shfile/complete$ export FIGNORE=.sh ch@ch:~/ch/shfile/complete$ ls func.sh log.txt test.txt ch@ch:~/ch/shfile/complete$ todo log.txt test.txt ch@ch:~/ch/shfile/complete$ todo
先執行
complete -r
清理所有規則;complete
查看規則;ls
查看文件,todo
補齊顯示所有文件; 定義變量FIGNORE
設置過濾文件; 再次todo
補全, 則無.sh
文件; 注意: 支持過濾;
-G
指定的文件擴張; 不過濾; 受FIGNORE
影響;ch@ch:~/ch/shfile/complete$ complete -r ch@ch:~/ch/shfile/complete$ complete ch@ch:~/ch/shfile/complete$ complete -G "*.txt" todo ch@ch:~/ch/shfile/complete$ todo log.txt test.txt ch@ch:~/ch/shfile/complete$ todo lo log.txt test.txt ch@ch:~/ch/shfile/complete$ todo lo
根據執行結果可以看到不會過濾匹配
lo
的;這種不建議使用;同樣受FIGNORE
影響, 可自行嘗試;
-W shellexpr
: 一般是一個字符串, 但是也會進行bash
擴張, 也就是說完全可以當成shell
進行編程, 只是沒有輸入而已; 但是會對輸出結果進行過濾;ch@ch:~/ch/shfile/complete$ complete -r ch@ch:~/ch/shfile/complete$ complete ch@ch:~/ch/shfile/complete$ complete -W "hello world $(echo good) $(for i in {1..9};do echo $i;done;) $((100+100))" todo ch@ch:~/ch/shfile/complete$ todo 1 2 200 3 4 5 6 7 8 9 good hello world ch@ch:~/ch/shfile/complete$ todo 2 2 200 ch@ch:~/ch/shfile/complete$ todo 2
可以看到在
-W
中進行了很多的shell
操作,bash
會進行指令擴張, 然後使用${IFS}
進行分詞; 分詞結果就是候選項;Readline
會進行排序, 可以通過開關關閉排序;-o nosort
;ch@ch:~/ch/shfile/complete$ complete -o nosort -W "hello world $(echo good) $(for i in {1..9};do echo $i;done;) $((100+100))" todo ch@ch:~/ch/shfile/complete$ todo hello world good 1 2 3 4 5 6 7 8 9 200
-F
實現未選項參數補齊; 不過濾; 但是可以根據輸入自行過濾;function _comp { echo $1:$2:$3:${2:2} >> log.txt COMPREPLY=( $(compgen -W "hello world" -- ${2:2}) ) echo reply:${COMPREPLY[@]} >> log.txt } complete -F _comp -P "--" todo
執行過程
ch@ch:~/ch/shfile/complete$ complete -r ch@ch:~/ch/shfile/complete$ source func.sh ch@ch:~/ch/shfile/complete$ complete complete -P '--' -F _comp todo ch@ch:~/ch/shfile/complete$ todo -- --hello --world ch@ch:~/ch/shfile/complete$ todo --hello
需要注意: 生成的候選列可能會替換修改,
$2
即補全單詞; 導致一些小bug; 去掉代碼中的${2:2}
就可以看到; 不懂compgen
什麼作用的可以直接執行指令compgen -f && ls
, 觀察輸出結果;
-X
過濾ch@ch:~/ch/shfile/complete$ complete -f -X '!&*' todo ch@ch:~/ch/shfile/complete$ todo func.sh log.txt test.txt tt.txt ch@ch:~/ch/shfile/complete$ todo t test.txt tt.txt ch@ch:~/ch/shfile/complete$ todo t
可以看到自行過濾一些;過濾匹配的, 前置
!
表示過濾不匹配的;這裏是常規匹配;下面試一下func.sh
不參與匹配;ch@ch:~/ch/shfile/complete$ complete -f -X '!&*' -X "*.sh" todo ch@ch:~/ch/shfile/complete$ todo log.txt test.txt tt.txt ch@ch:~/ch/shfile/complete$ todo
可以看到
sh
結尾的被過濾掉了;-X '!&*'
爲什麼是單引號? 因爲雙引號會進行bash
處理,!
有特殊含義;
-P
添加前綴, 選項參數function _comp { echo $1:$2:$3:${2:2} >> log.txt COMPREPLY=( $(compgen -W "hello world" -- ${2:2}) ) echo reply:${COMPREPLY[@]} >> log.txt } complete -F _comp -P "--" todo
和上面的重複直接拷貝了; 直接看下面的執行結果;
ch@ch:~/ch/shfile/complete$ complete -r ch@ch:~/ch/shfile/complete$ complete ch@ch:~/ch/shfile/complete$ source func.sh ch@ch:~/ch/shfile/complete$ complete complete -P '--' -F _comp todo ch@ch:~/ch/shfile/complete$ todo -- --hello --world ch@ch:~/ch/shfile/complete$ todo --hello
可以看到爲所有結果加了前綴
--
; 然後進行匹配;
動態加載: 減少一次性加載耗時,規則搜索慢問題;這裏簡單介紹一下動態修改;
_COMPFLAG=124 function _comp { echo $1:$2:$3:${2:2} >> log.txt COMPREPLY=( $(compgen -W "hello world" -- ${2:2}) ) echo reply:${COMPREPLY[@]} >> log.txt compopt -o plusdirs $1 local temp=${_COMPFLAG} _COMPFLAG=0 echo ${temp} >> log.txt return ${temp} } complete -F _comp -P "--" todo
ch@ch:~/ch/shfile/complete$ complete ch@ch:~/ch/shfile/complete$ complete -r ch@ch:~/ch/shfile/complete$ complete ch@ch:~/ch/shfile/complete$ source func.sh ch@ch:~/ch/shfile/complete$ complete complete -P '--' -F _comp todo ch@ch:~/ch/shfile/complete$ todo --hello temp/ --world ch@ch:~/ch/shfile/complete$ complete complete -o plusdirs -P '--' -F _comp todo
可以看到前後變化; 和官方有點出入;如果不用
_COMPFLAG
機制, 會一直重新加載; 無任何生成;_completion_loader() { . "/etc/bash_completion.d/$1.sh" >/dev/null 2>&1 && return 124 } complete -D -F _completion_loader -o bashdefault -o default