基于golang語言開發(fā)命令行工具(command line interfaces,CLI)最常用的框架是Cobra稚晚。通過Cobra可以使用簡單的接口實(shí)現(xiàn)一個(gè)強(qiáng)大現(xiàn)代的CLI工具烛缔,許多知名的項(xiàng)目比如Docker、Kubernetes等都用Cobra來開發(fā)自己的命令行工具。下面我們將對Cobra的基本用法做簡要介紹。
- 概覽
- 相關(guān)概念
- Commands(命令)
- Flags(標(biāo)志)
- 安裝
- 開始使用
- 使用Cobra生成器
- 使用Cobra庫
- 使用Flags
- 位置和自定義參數(shù)
- 示例
- Help Command
- 展示Usage
- 預(yù)處理和后處理等Hooks函數(shù)
- 處理 unknown command 的建議
- 生成命令行文檔
- 實(shí)現(xiàn)命令自動補(bǔ)全
概覽
Cobra提供簡單的接口來創(chuàng)建強(qiáng)大的現(xiàn)代化CLI接口,比如git與go。Cobra同時(shí)也提供一個(gè)二進(jìn)制工具路狮,用于創(chuàng)建命令行程序。
Cobra提供:
- 簡單的子命令模式: 如
app server
蔚约,app fetch
等 - flags兼容posix模式(包括長奄妨、短版本)
- 支持子命令嵌套
- 支持全局、局部以及繼承falgs
- 智能提示苹祟,如
app srver
砸抛,將提示srver子命令不存在,是否為app server
- 自動生成子命令及其flags
- 自動生成
-h
树枫,--help
等flags提醒 - 自動生成命令行docs和man文件
- 支持命令行別名
- 靈活定義help和usage信息
- 可與viper庫結(jié)合使用直焙,方便參數(shù)、配置和環(huán)境變量的管理
相關(guān)概念
Cobra的構(gòu)建基于結(jié)構(gòu)化的commands砂轻,arguments和flags奔誓,即命令、參數(shù)和標(biāo)志搔涝。
Commands代表命令行程序執(zhí)行什么命令厨喂,Args(即arguments)和Flags即這些命令的修飾符。
最好的命令行程序應(yīng)該像讀句子那樣去使用体谒,用戶通過命令行本身就可以自然地知道這段命令執(zhí)行的是什么操作杯聚。
一個(gè)典型的命令行設(shè)計(jì)模式類似APPNAME COMMAND ARG --FLAG
或APPNAME VERB NOUN --ADJECTIVE
。
例如下面的例子抒痒,‘server’是一個(gè)操作幌绍, ‘port’是一個(gè)標(biāo)志:
hugo server --port=1313
下面的另一個(gè)例子告訴我們,它要通過Git來執(zhí)行clone操作拷貝url對應(yīng)的項(xiàng)目到本地的裸倉庫:
git clone URL --bare
Commands(命令)
Command是一個(gè)命令行程序最重要的概念。命令行程序支持的每一個(gè)交互操作都應(yīng)該被包含在一個(gè)command中傀广。一條command可以有多條子commands并且能夠可選地執(zhí)行它們颁独。
在上面的例子中, ‘server’就是一條command伪冰。
點(diǎn)擊查看更多有關(guān)Cobra Command的信息
Flags(標(biāo)志)
一個(gè)Flag用來控制command的行為誓酒。Cobra完全兼容POSIX的flags模式,如同Go自帶的標(biāo)準(zhǔn)flag庫那樣贮聂。一條Cobra生成的command可以將它的flags繼承給它的子commadn靠柑,也可以限定這些flags只能被該command使用。
在上面的例子中吓懈,‘port’即一個(gè)flag歼冰。
更加強(qiáng)大的flag功能可以使用pflag庫,它是一個(gè)標(biāo)準(zhǔn)flag庫的擴(kuò)展耻警。
安裝
使用Cobra十分簡單隔嫡。首先通過go get
安裝最新版本的代碼庫及相關(guān)依賴,這條命令同時(shí)也會安裝cobra
可執(zhí)行程序:
go get -u github.com/spf13/cobra/cobra
接下來甘穿,在golang代碼中引用Cobra:
import "github.com/spf13/cobra"
開始使用
通常一個(gè)Cobra程序遵循如下所示的組織結(jié)構(gòu)腮恩。當(dāng)然,你也可以自己定義合適的結(jié)構(gòu)温兼。
? appName/
? cmd/
add.go
your.go
commands.go
here.go
main.go
Cobra程序中的main.go文件非常簡單秸滴,它通常只做一件事,就是初始化Cobra妨托。
package main
import (
"{pathToYourApp}/cmd"
)
func main() {
cmd.Execute()
}
使用Cobra生成器
Cobra提供一個(gè)命令行程序cobra
來快速生成你想要的任何命令行程序的框架缸榛。本文不介紹Cobra生成器的使用吝羞,關(guān)于該工具的詳細(xì)信息點(diǎn)擊此處兰伤。
使用Cobra庫
使用Cobra,需要創(chuàng)建一個(gè)main.go文件和一個(gè)rootCmd文件钧排。當(dāng)然敦腔,你也可以選擇別的地方去添加額外的commands。
創(chuàng)建rootCmd
Cobra沒有構(gòu)造函數(shù)恨溜,所以直接簡單地創(chuàng)建一個(gè)command對象就行符衔。
這里假設(shè)下面的代碼位于app/cmd/root.go文件:
var rootCmd = &cobra.Command{
Use: "hugo",
Short: "Hugo is a very fast static site generator",
Long: `A Fast and Flexible Static Site Generator built with
love by spf13 and friends in Go.
Complete documentation is available at http://hugo.spf13.com`,
Run: func(cmd *cobra.Command, args []string) {
// Do Stuff Here
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
你也可以在init()函數(shù)中定義和處理flags和配置。
例如 cmd/root.go:
package cmd
import (
"fmt"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
// Used for flags.
cfgFile string
userLicense string
rootCmd = &cobra.Command{
Use: "cobra",
Short: "A generator for Cobra based Applications",
Long: `Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
}
)
// Execute executes the root command.
func Execute() error {
return rootCmd.Execute()
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "author name for copyright attribution")
rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project")
rootCmd.PersistentFlags().Bool("viper", true, "use Viper for configuration")
viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))
viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
viper.SetDefault("license", "apache")
rootCmd.AddCommand(addCmd)
rootCmd.AddCommand(initCmd)
}
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
er(err)
}
// Search config in home directory with name ".cobra" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".cobra")
}
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}
創(chuàng)建main.go
在cmd/root.go文件中我們已經(jīng)定義了一個(gè)名為rootCmd的command作為根糟袁,為了執(zhí)行該command判族,還需要將其放入main.go文件中執(zhí)行。
在一個(gè)Cobra程序中项戴,main.go文件通常只干一件事形帮,即初始化Cobra并執(zhí)行command。
package main
import (
"{pathToYourApp}/cmd"
)
func main() {
cmd.Execute()
}
添加其他command
不同的command通常分別在不同的go文件中定義,這里假設(shè)所有command的實(shí)現(xiàn)都處于cmd/目錄下不同的文件中辩撑。
例如界斜,如果你想創(chuàng)建名為version的command,可以創(chuàng)建cmd/version.go文件合冀,并在文件里這么寫:
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of Hugo",
Long: `All software has versions. This is Hugo's`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
},
}
使用Flags
Flags提供控制command行為的能力各薇。
給command綁定flags
一般情況下,我們需要預(yù)先定義變量來存儲flags的值君躺,這樣便于我們各處使用它們(Cobra也支持不顯式地定義和使用flags峭判,但這種情況我們后面再說)。
如下棕叫,我們定義了Verbose和Source兩個(gè)不同類型的變量來表示和存儲flags值朝抖。
var Verbose bool
var Source string
Cobra有兩種不同的方式給command綁定flags,分別為Persistent Flags和Local Flags谍珊。
Persistent Flags( 持久型flags)
Persistent Flags表示flag不僅綁定在一個(gè)command上治宣,同時(shí)也綁定在了這個(gè)command的子command上。Persistent Flags可以被一個(gè)command下的所有子command使用砌滞。
例如侮邀,我們給根command(即rootCmd)綁定了一個(gè)名為‘verbose’的Persistent Flag,那么這個(gè)flag就成了一個(gè)全局flag贝润,可以被所有command使用(因?yàn)閞ootCommand為根command绊茧,顯然后續(xù)所有增加的command都為子command)。
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
Local Flags(本地flags)
Local Flags表示flag僅能被其綁定的command使用打掘。
如下我們將名為'source'的flag綁定給一個(gè)名為localCmd的command华畏,除了localCmd外,其他command無法接收到這個(gè)flag的值尊蚁,即只有l(wèi)oalCmd能給‘Source’變量通過指定flag來賦值亡笑。
localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
command定義的Local Flags只能由綁定了這些flags的command使用,但這里還有一種方便的方式横朋,可以使得一個(gè)command所有的子command定的flags都綁定給他們的父command仑乌,只要在創(chuàng)建command時(shí),指定TraverseChildren為true即可琴锭,這樣父command在真正執(zhí)行前會遍歷其所有的子command來綁定flags晰甚。
command := cobra.Command{
Use: "print [OPTIONS] [COMMANDS]",
TraverseChildren: true,
}
給flags綁定配置
在使用命令行程序時(shí)不總會顯式地提供flags的值,有時(shí)希望程序自己去環(huán)境變量决帖、配置文件等地方自動尋找配置參數(shù)厕九。這時(shí)我們可以通過viper庫來實(shí)現(xiàn)這一功能,將flags的值綁定給viper地回。viper庫是一個(gè)配置管理庫扁远,它可以方便地從配置文件腺阳、環(huán)境變量和遠(yuǎn)端等多種源來獲取配置。
var author string
func init() {
rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution")
viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
}
在上面例子中我們將author這個(gè)flag綁定給了viper穿香,viper一般會按照flag亭引、環(huán)境變量、配置文件和默認(rèn)值的優(yōu)先級去尋找環(huán)境變量皮获,當(dāng)用戶沒有通過--author
給出flag值時(shí)焙蚓,viper會降低優(yōu)先級去別處尋找匹配的參數(shù)值。
Required flags
默認(rèn)情況下洒宝,是否指定flags是可選的购公,如果你希望當(dāng)一個(gè)flag沒有設(shè)置時(shí),命令行報(bào)錯雁歌,你可以標(biāo)記它為必須的
rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
rootCmd.MarkFlagRequired("region")
位置和自定義參數(shù)
驗(yàn)證和限制命令行程序的位置參數(shù)可以通過Command
的Args
字段來實(shí)現(xiàn)宏浩。
Cobra內(nèi)置有下列驗(yàn)證函數(shù)給Args
字段使用:
-
NoArgs
- 不允許有位置參數(shù) -
ArbitraryArgs
- 可以接受任意多個(gè)位置參數(shù) -
OnlyValidArgs
- 只允許指定Command
在ValidArgs
字段里指定的位置參數(shù) -
MinumumNArgs(int)
- 限定最少提供多少個(gè)位置參數(shù) -
MaximumNArgs(int)
- 限定最多能提供多少個(gè)位置參數(shù) -
ExactArgs(int)
- 限定必須提供多少個(gè)對應(yīng)的位置參數(shù) -
ExcatValidArgs(int)
- 限定必須提供多少個(gè)對應(yīng)的位置參數(shù),并且位置參數(shù)必須位于ValidArgs
字段 -
RangeArgs(min, max)
- 限定位置參數(shù)的個(gè)數(shù)必須處于某一個(gè)區(qū)間內(nèi)
以下為一個(gè)限制位置參數(shù)的例子:
var cmd = &cobra.Command{
Short: "hello",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("requires a color argument")
}
if myapp.IsValidColor(args[0]) {
return nil
}
return fmt.Errorf("invalid color specified: %s", args[0])
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hello, World!")
},
}
示例
目前為止我們已經(jīng)介紹了很多Cobra的基本用法靠瞎,本節(jié)會給出一個(gè)完整的例子來回顧前面講過的知識比庄。
在下面的例子中,我們定義了3個(gè)commands乏盐。兩個(gè)commands為頂級命令佳窑,一個(gè)command為頂級命令的子命令。在這個(gè)例子中父能,由于rootCmd
沒有為Run字段提供方法神凑,所以單獨(dú)的root是不能運(yùn)行的,必須要有子commands何吝。
注意這里我們只為一個(gè)名為echoTimes的command 設(shè)置了flag溉委。更多flags的用法參考https://github.com/spf13/pflag。
package main
import (
"fmt"
"strings"
"github.com/spf13/cobra"
)
func main() {
var echoTimes int
var cmdPrint = &cobra.Command{
Use: "print [string to print]",
Short: "Print anything to the screen",
Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdEcho = &cobra.Command{
Use: "echo [string to echo]",
Short: "Echo anything to the screen",
Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Echo: " + strings.Join(args, " "))
},
}
var cmdTimes = &cobra.Command{
Use: "times [string to echo]",
Short: "Echo anything to the screen more times",
Long: `echo things multiple times back to the user by providing
a count and a string.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
for i := 0; i < echoTimes; i++ {
fmt.Println("Echo: " + strings.Join(args, " "))
}
},
}
cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")
var rootCmd = &cobra.Command{Use: "app"}
rootCmd.AddCommand(cmdPrint, cmdEcho)
cmdEcho.AddCommand(cmdTimes)
rootCmd.Execute()
}
如果想要參考更完整和大型的例子爱榕,請點(diǎn)擊Hugo瓣喊。
Help Command
當(dāng)你的程序有子命令時(shí),Cobra 會自動給你程序添加help命令呆细。當(dāng)你運(yùn)行app help
型宝,會調(diào)用help命令。另外絮爷,help同樣支持其它輸入命令。例如梨树,你有一個(gè)沒有任何其它配置的命令叫create
坑夯,當(dāng)你調(diào)用app help create
Corbra 將會起作用。
例子
下面的輸出是Cobra自動生成的抡四,除了command和flag的定義柜蜈,我們沒有對command做任何其他定制仗谆。
$ cobra help
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
Usage:
cobra [command]
Available Commands:
add Add a command to a Cobra Application
help Help about any command
init Initialize a Cobra Application
Flags:
-a, --author string author name for copyright attribution (default "YOUR NAME")
--config string config file (default is $HOME/.cobra.yaml)
-h, --help help for cobra
-l, --license string name of license for the project
--viper use Viper for configuration (default true)
Use "cobra [command] --help" for more information about a command.
help 就跟其它命令一樣,并沒有特殊的邏輯或行為淑履。事實(shí)上隶垮,你也可以提供你自己定義的help。
自定義Help
你可以使用下面的函數(shù)來定義自己的help:
cmd.SetHelpCommand(cmd *Command)
cmd.SetHelpFunc(f func(*Command, []string))
cmd.SetHelpTemplate(s string)
后兩個(gè)函數(shù)定義的help會被command的子命令繼承秘噪。
展示Usage
當(dāng)用戶錯誤的使用命令行程序時(shí)(如指定非法的flag和command)狸吞,Cobra將會自動顯示命令行程序的用法說明usage
。
例子
$ cobra --invalid
Error: unknown flag: --invalid
Usage:
cobra [command]
Available Commands:
add Add a command to a Cobra Application
help Help about any command
init Initialize a Cobra Application
Flags:
-a, --author string author name for copyright attribution (default "YOUR NAME")
--config string config file (default is $HOME/.cobra.yaml)
-h, --help help for cobra
-l, --license string name of license for the project
--viper use Viper for configuration (default true)
Use "cobra [command] --help" for more information about a command.
自定義用法說明
你能提供你自己的usage函數(shù)或模板給 Cobra 使用指煎。
類似于自定義help蹋偏,usage的方法和模板都會覆蓋默認(rèn)的公共說明。
cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)
版本Flag
當(dāng)頂級Command的Version字段被定義后至壤,Cobra會自動為頂級command添加一個(gè)--version
flag威始。當(dāng)命令行程序指定該flag時(shí),Cobra會調(diào)用內(nèi)置的版本函數(shù)打印命令行程序的相關(guān)版本信息像街。當(dāng)然黎棠,你也可以使用cmd.SetVersionTemplate(s string)
函數(shù)來自定義版本信息的展示內(nèi)容。
預(yù)處理和后處理等Hooks函數(shù)
Cobra提供了多個(gè)鉤子(hooks)函數(shù)的接口镰绎,你可以很容易地去決定在command執(zhí)行Run中的實(shí)際函數(shù)之前或之后葫掉,需要執(zhí)行哪些方法。
PersistentPreRun
和PreRun
函數(shù)會在Run
之前執(zhí)行跟狱。
PersistentPostRun
和PostRun
函數(shù)將會在Run
之后執(zhí)行俭厚。
Persistent*Run
這種模式的函數(shù)會被子command繼承。
鉤子函數(shù)的執(zhí)行順序如下:
PersistentPreRun
PreRun
Run
PostRun
PersistentPostRun
如下的例子使用了上面提到的所有鉤子函數(shù)驶臊。需要注意的是挪挤,當(dāng)子command執(zhí)行時(shí),它會執(zhí)行根command的PersistentPreRun
函數(shù)而不會執(zhí)行根command的PersistentPostRun
函數(shù)(因?yàn)樽觕ommand自己定義了該函數(shù))关翎。
package main
import (
"fmt"
"github.com/spf13/cobra"
)
func main() {
var rootCmd = &cobra.Command{
Use: "root [sub]",
Short: "My root command",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
},
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd Run with args: %v\n", args)
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
},
}
var subCmd = &cobra.Command{
Use: "sub [no options!]",
Short: "My subcommand",
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside subCmd PreRun with args: %v\n", args)
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside subCmd Run with args: %v\n", args)
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside subCmd PostRun with args: %v\n", args)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args)
},
}
rootCmd.AddCommand(subCmd)
rootCmd.SetArgs([]string{""})
rootCmd.Execute()
fmt.Println()
rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
rootCmd.Execute()
}
輸出:
Inside rootCmd PersistentPreRun with args: []
Inside rootCmd PreRun with args: []
Inside rootCmd Run with args: []
Inside rootCmd PostRun with args: []
Inside rootCmd PersistentPostRun with args: []
Inside rootCmd PersistentPreRun with args: [arg1 arg2]
Inside subCmd PreRun with args: [arg1 arg2]
Inside subCmd Run with args: [arg1 arg2]
Inside subCmd PostRun with args: [arg1 arg2]
Inside subCmd PersistentPostRun with args: [arg1 arg2]
處理 unknown command 的建議
Cobra在unknown command錯誤發(fā)生時(shí)扛门,會自動打印建議。這就讓Cobra的處理錯誤行為的方式類似git
命令那樣纵寝。例如:
$ hugo srever
Error: unknown command "srever" for "hugo"
Did you mean this?
server
Run 'hugo --help' for usage.
建議會基于注冊的子命令自動生成论寨。使用了Levenshtein distance的實(shí)現(xiàn)。每一個(gè)模糊匹配的命令間隔為2個(gè)字符爽茴。
如果你希望在你的命令里葬凳,禁用建議或減小字符串的距離,使用:
command.DisableSuggestions = true
或
command.SuggestionsMinimumDistance = 1
你也可以通過SuggestFor
來給命令提供明確的名詞建議室奏。這個(gè)特性允許當(dāng)字符串不相近火焰,但是意思與你的命令相近時(shí),提供指定的命令建議胧沫,比如:
$ kubectl remove
Error: unknown command "remove" for "kubectl"
Did you mean this?
delete
Run 'kubectl help' for usage.
生成命令行文檔
Cobra可以基于command昌简、flags等來自動生成文檔占业,支持下面幾種格式:
實(shí)現(xiàn)命令自動補(bǔ)全
Cobra還提供了自動生成bash或zsh自動補(bǔ)全腳本的功能,這部分參見