
前言
用過類 Unix 系統中 Unix shell(Shell/Bash/Zsh) 的同學都應該對 TAB 鍵印象深刻,因為它可以幫忙補全或提示後續的指令,使用者不用記住完整的指令,隻需輸入前幾個字元,按 TAB 鍵,就會提示後續的指令供使用者選擇,使用者體驗極佳。目前流行的一些使用 Go 語言開發的 CLI 工具,如
kubectl
和
helm
,他們也都有
completion
也就是指令自動補全功能,通過将
source <(kubectl completion zsh)
加入
.zshrc
檔案中,就可以在每次啟動 shell 時自動加載自動補全腳本,之後就可以體驗到與原生 shell 相同的自動補全功能了。這些 CLI 工具,都是基于
Cobra庫開發,指令自動補全功能也是該庫提供的一個功能,本篇文章就來講講如何使用 Cobra 實作指令自動補全的。
Cobra Shell Completion
Cobra 可以作為一個 Golang 包,用來建構功能強大的指令行程式;同時也可以作為 CLI 工具,用來生成應用程式和指令檔案。
由于文本主要介紹 Cobra 的指令自動補全功能,更多内容請查閱
官網。
基礎用法
Cobra 目前的最新版本為
v1.0.0
,支援生成多種 Shell 的自動補全腳本,目前支援:
- Bash
- Zsh
- Fish
- PowerShell
如上所述,Cobra 不但是一個功能強大的 Golang 包,還是一個 CLI 工具,可以用來生成應用程式和指令檔案。使用如下指令,即可生成用于指令自動補全的代碼:
$ cobra add completion
或者也可以建立
cmd/completion.go
檔案,來放置用于生成指令自動補全腳本的代碼:
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: `To load completions:
Bash:
$ source <(yourprogram completion bash)
# To load completions for each session, execute once:
Linux:
$ yourprogram completion bash > /etc/bash_completion.d/yourprogram
MacOS:
$ yourprogram completion bash > /usr/local/etc/bash_completion.d/yourprogram
Zsh:
# If shell completion is not already enabled in your environment you will need
# to enable it. You can execute the following once:
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
# To load completions for each session, execute once:
$ yourprogram completion zsh > "${fpath[1]}/_yourprogram"
# You will need to start a new shell for this setup to take effect.
Fish:
$ yourprogram completion fish | source
# To load completions for each session, execute once:
$ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "bash":
cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
cmd.Root().GenPowerShellCompletion(os.Stdout)
}
},
}
官方推薦将生成内容輸出到
os.Stdout
,隻需上面這些簡單的指令,即可在你的 CLI 工具中新增
completion
子指令,執行該指令即可生成相應 Shell 的指令自動補全腳本,将其插入或儲存到相應 Shell 的指定位置即可實作指令自動補全功能。
如果加載了配置檔案, os.Stdout
可能會列印多餘的資訊,這會導緻自動補全腳本失效,是以請避免這種情況。
進階用法
上面的這些隻是基本用法,完成的隻是指令補全的基本功能,但一些定制化的需求是無法實作的。比如,
kubectl get [tab]
這裡的預期内容是傳回所有 k8s 資源名稱,但是隻靠上面的代碼是無法實作的。這裡就需要用到自定義補全,通過為每個指令增加不同的參數或方法,可以實作靜态和動态補全等功能。
名稱補全
名稱補全其實也分靜态名稱和動态名稱,靜态名稱就像
kubectl completion [tab]
預期傳回的多種 shell 名稱,内容為事先在代碼中已經定義好的内容;而動态名稱,就是像
helm status [tab]
預期傳回的所有 release 名稱,并不是以靜态内容展現,而是通過函數動态擷取的内容。
靜态名稱補全
靜态名稱補全比較簡單,隻要在想要自動補全的子指令中加入
ValidArgs
字段,傳入一組包含預期結果的字元串數組即可,代碼如下:
validArgs []string = { "pod", "node", "service", "replicationcontroller" }
cmd := &cobra.Command{
Use: "get [(-o|--output=)json|yaml|template|...](RESOURCE [NAME] | RESOURCE/NAME ...)",
Short: "Display one or many resources",
Long: get_long,
Example: get_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunGet(f, out, cmd, args)
util.CheckErr(err)
},
ValidArgs: validArgs,
}
這裡是模仿 kubectl 的
get
子指令,在執行該指令時效果如下:
$ kubectl get [tab][tab]
node pod replicationcontroller service
如果指令有别名(Aliases)的話,則可以使用
ArgAliases
,代碼如下:
argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" }
cmd := &cobra.Command{
...
ValidArgs: validArgs,
ArgAliases: argAliases
}
别名不會在按 TAB 時提示給使用者,但如果手動輸入,則補全算法會将其視為有效參數,并提供後續的補全。
$ kubectl get rc [tab][tab]
backend frontend database
這裡如果不聲明
rc
為别名,則補全算法将無法補全後續的内容。
動态名稱補全
如果需要補全的名稱是動态生成的,例如
helm status [tab]
這裡的
release
值,就需要用到
ValidArgsFunction
字段,将需要傳回的内容以 function 的形式聲明在
cobra.Command
中,代碼如下:
cmd := &cobra.Command{
Use: "status RELEASE_NAME",
Short: "Display the status of the named release",
Long: status_long,
RunE: func(cmd *cobra.Command, args []string) {
RunGet(args[0])
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return getReleasesFromCluster(toComplete), cobra.ShellCompDirectiveNoFileComp
},
}
上面這段代碼是
helm
的源碼,也是 Cobra 的官方示例代碼,很好的展示了這個 function 的結構及傳回格式,有興趣的同學可以去看一下
helm
的源碼,也是很有意思的。
getReleasesFromCluster
方法是用來擷取 Helm release 清單,在執行指令時,效果如下:
$ helm status [tab][tab]
harbor notary rook thanos
cobra.ShellCompDirective
可以控制自動補全的特定行為,你可以用或運算符來組合它們,像這樣
cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
,下面是它們的介紹(摘自官方文檔):
// Indicates that the shell will perform its default behavior after completions
// have been provided (this implies none of the other directives).
ShellCompDirectiveDefault
// Indicates an error occurred and completions should be ignored.
ShellCompDirectiveError
// Indicates that the shell should not add a space after the completion,
// even if there is a single completion provided.
ShellCompDirectiveNoSpace
// Indicates that the shell should not provide file completion even when
// no completion is provided.
ShellCompDirectiveNoFileComp
// Indicates that the returned completions should be used as file extension filters.
// For example, to complete only files of the form *.json or *.yaml:
// return []string{"yaml", "json"}, ShellCompDirectiveFilterFileExt
// For flags, using MarkFlagFilename() and MarkPersistentFlagFilename()
// is a shortcut to using this directive explicitly.
//
ShellCompDirectiveFilterFileExt
// Indicates that only directory names should be provided in file completion.
// For example:
// return nil, ShellCompDirectiveFilterDirs
// For flags, using MarkFlagDirname() is a shortcut to using this directive explicitly.
//
// To request directory names within another directory, the returned completions
// should specify a single directory name within which to search. For example,
// to complete directories within "themes/":
// return []string{"themes"}, ShellCompDirectiveFilterDirs
//
ShellCompDirectiveFilterDirs
ValidArgs
同時隻能存在一個。在使用
ValidArgsFunction
時,Cobra 将在解析了指令行中提供的所有 flag 和參數之後才會調用您的注冊函數。
ValidArgsFunction
Flag 補全
指定必選 flag
大多時候,名字補全隻會提示子指令的補全,但如果一些 flag 是必須的,也可以在使用者按 TAB 鍵時進行自動補全,代碼如下:
cmd.MarkFlagRequired("pod")
cmd.MarkFlagRequired("container")
然後在執行指令時,就可以看到:
$ kubectl exec [tab][tab]
-c --container= -p --pod=
動态 flag
同名稱補全類似,Cobra 提供了一個字段來完成該功能,需要使用
command.RegisterFlagCompletionFunc()
來注冊自動補全的函數,代碼如下:
flagName := "output"
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"json", "table", "yaml"}, cobra.ShellCompDirectiveDefault
})
RegisterFlagCompletionFunc()
是通過
command
與該 flag 的進行關聯的,在本示例中可以看到:
$ helm status --output [tab][tab]
json table yaml
使用方式和名稱補全相同,這裡就不做詳細介紹了。
Debug
指令自動補全與其他功能不同,調試起來比較麻煩,是以 Cobra 提供了調用隐藏指令,模拟自動補全腳本的方式來幫助調試代碼,你可以直接使用以下隐藏指令來模拟觸發:
$ helm __complete status har[ENTER]
harbor
:4
Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
如果需要提示名稱而非補全(就是輸入指令後直接按 TAB 鍵),則必須将空參數傳遞給
__complete
指令:
$ helm __complete status ""[ENTER]
harbor
notary
rook
thanos
:4
Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
同樣可以用來調試 flag 的自動補全:
$ helm __complete status --output ""[ENTER]
json
table
yaml
:4
Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr
結語
以上内容是作者挑選的一些較為常用的功能,更多的内容詳見
官方文檔。如果想看示例的話,推薦
kubectl helm的源碼。
當然 Cobra 還不是完美的,比如生成的 Zsh 腳本有些問題,
kubectl
helm
都是使用将其生成的 Bash 自動補全腳本轉化為 Zsh 的自動補全腳本的方式。但不得不承認,Cobra 是一個非常好用的 CLI 工具建構架構,很多流行的 CLI 工具都是使用它來建構的,這也是為什麼使用 GO 語言編寫的 CLI 工具如雨後春筍般快速的出現并占據了雲原生工具的關鍵位置。