天天看點

Cobra 指令自動補全指北前言Cobra Shell Completion結語

Cobra 指令自動補全指北前言Cobra Shell Completion結語

前言

用過類 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 指令自動補全指北前言Cobra Shell Completion結語

基礎用法

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

同時隻能存在一個。在使用

ValidArgsFunction

時,Cobra 将在解析了指令行中提供的所有 flag 和參數之後才會調用您的注冊函數。

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 工具如雨後春筍般快速的出現并占據了雲原生工具的關鍵位置。

Cobra 指令自動補全指北前言Cobra Shell Completion結語

繼續閱讀