bash 指令自動補全

簡介

  • 什麼是補全, 什麼是補全規則;

  • 補全規則怎麼選;

  • 補全規則怎麼生成候選項;

  • compgen, complete, compopt指令;

  • 案例

項目路徑

什麼是補全

  • 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: 提供basharray類型變量名到候選項;

  • -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 stasta的前一個單詞git; 輸出按\n分詞納入候選集合; 集合不會被過濾;

-F function

  • -F, 輸出通過COMPREPLY變量指定; 變量是array類型; 不會被git sta過v了;

-G globpat

  • 文件擴展, 具體參上; 不會被git stasta過濾;

-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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章