go語言Pflag Viper Cobra 核心功能使用介紹
1.如何構(gòu)建應(yīng)用框架
一般來說構(gòu)建應(yīng)用框架包含3個部分:
- 命令行參數(shù)解析
- 配置文件解析
- 應(yīng)用的命令行框架:需要具備 Help 功能、需要能夠解析命令行參數(shù)和配置文件、命令需要能夠初始化業(yè)務(wù)代碼,并最終啟動業(yè)務(wù)進(jìn)程 上3個需求便涉及Pflag、Viper、Cobra的使用,并且這三個包也是相互聯(lián)系滴
2.命令行參數(shù)解析工具:Pflag
雖然 Go 源碼中提供了一個標(biāo)準(zhǔn)庫 Flag 包,用來對命令行參數(shù)進(jìn)行解析,但在大型項(xiàng)目中應(yīng)用更廣泛的是另外一個包:Pflag
2.1 Pflag 包 Flag 定義
Pflag 可以對命令行參數(shù)進(jìn)行處理,一個命令行參數(shù)在 Pflag 包中會解析為一個 Flag 類型的變量
type Flag struct { Name string // flag長選項(xiàng)的名稱 Shorthand string // flag短選項(xiàng)的名稱,一個縮寫的字符 Usage string // flag的使用文本 Value Value // flag的值 DefValue string // flag的默認(rèn)值 Changed bool // 記錄flag的值是否有被設(shè)置過 NoOptDefVal string // 當(dāng)flag出現(xiàn)在命令行,但是沒有指定選項(xiàng)值時的默認(rèn)值 Deprecated string // 記錄該flag是否被放棄 Hidden bool // 如果值為true,則從help/usage輸出信息中隱藏該flag ShorthandDeprecated string // 如果flag的短選項(xiàng)被廢棄,當(dāng)使用flag的短選項(xiàng)時打印該信息 Annotations map[string][]string // 給flag設(shè)置注解 }
Flag 的值是一個 Value 類型的接口,Value 定義如下
type Value interface { String() string // 將flag類型的值轉(zhuǎn)換為string類型的值,并返回string的內(nèi)容 Set(string) error // 將string類型的值轉(zhuǎn)換為flag類型的值,轉(zhuǎn)換失敗報錯 Type() string // 返回flag的類型,例如:string、int、ip等 }
2.2 Pflag 包 FlagSet 定義
Pflag 除了支持單個的 Flag 之外,還支持 FlagSet。FlagSet 是一些預(yù)先定義好的 Flag 的集合
- 調(diào)用 NewFlagSet 創(chuàng)建一個 FlagSet
- 使用 Pflag 包定義的全局 FlagSet:CommandLine
var version bool flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) flagSet.BoolVar(&version, "version", true, "Print version information and quit.") ================================= import ( "github.com/spf13/pflag" ) pflag.BoolVarP(&version, "version", "v", true, "Print version information and quit.") func BoolVarP(p *bool, name, shorthand string, value bool, usage string) { flag := CommandLine.VarPF(newBoolValue(value, p), name, shorthand, usage) flag.NoOptDefVal = "true" }
2.3 Pflag 使用方法
- 支持多種命令行參數(shù)定義方式
// 支持長選項(xiàng)、默認(rèn)值和使用文本,并將標(biāo)志的值存儲在指針中。 var name = pflag.String("name", "colin", "Input Your Name") // 支持長選項(xiàng)、短選項(xiàng)、默認(rèn)值和使用文本,并將標(biāo)志的值存儲在指針中 var name = pflag.StringP("name", "n", "colin", "Input Your Name") // 支持長選項(xiàng)、默認(rèn)值和使用文本,并將標(biāo)志的值綁定到變量。 var name string pflag.StringVar(&name, "name", "colin", "Input Your Name") // 支持長選項(xiàng)、短選項(xiàng)、默認(rèn)值和使用文本,并將標(biāo)志的值綁定到變量。 var name string pflag.StringVarP(&name, "name", "n","colin", "Input Your Name") // 函數(shù)名帶Var說明是將標(biāo)志的值綁定到變量,否則是將標(biāo)志的值存儲在指針中 // 函數(shù)名帶P說明支持短選項(xiàng),否則不支持短選項(xiàng)。
- 使用Get獲取參數(shù)的值。
i, err := flagset.GetInt("flagname")
- 獲取非選項(xiàng)參數(shù)。
package main import ( "fmt" "github.com/spf13/pflag" ) var ( flagvar = pflag.Int("flagname", 1234, "help message for flagname") ) // 在定義完標(biāo)志之后,可以調(diào)用pflag.Parse()來解析定義的標(biāo)志。解析后,可通過pflag.Args()返回所有的非選項(xiàng)參數(shù),通過pflag.Arg(i)返回第 i 個非選項(xiàng)參數(shù)。參數(shù)下標(biāo) 0 到 pflag.NArg() - 1。 func main() { pflag.Parse() fmt.Printf("argument number is: %v\n", pflag.NArg()) fmt.Printf("argument list is: %v\n", pflag.Args()) fmt.Printf("the first argument is: %v\n", pflag.Arg(0)) }
- 指定了選項(xiàng)但是沒有指定選項(xiàng)值時的默認(rèn)值。
var ip = pflag.IntP("flagname", "f", 1234, "help message") pflag.Lookup("flagname").NoOptDefVal = "4321" --flagname=1357 ==> 1357 --flagname ==> 4321 [nothing] ==> 1234
- 棄用標(biāo)志或者標(biāo)志的簡寫
// deprecate a flag by specifying its name and a usage message pflag.CommandLine.MarkDeprecated("logmode", "please use --log-mode instead")
- 保留名為 port 的標(biāo)志,但是棄用它的簡寫形式
pflag.IntVarP(&port, "port", "P", 3306, "MySQL service host port.") // deprecate a flag shorthand by specifying its flag name and a usage message pflag.CommandLine.MarkShorthandDeprecated("port", "please use --port only")
- 隱藏標(biāo)志。
// 可以將 Flag 標(biāo)記為隱藏的,這意味著它仍將正常運(yùn)行,但不會顯示在 usage/help 文本中。 // hide a flag by specifying its name pflag.CommandLine.MarkHidden("secretFlag")
3.配置解析神器:Viper
幾乎所有的后端服務(wù),都需要一些配置項(xiàng)來配置我們的服務(wù);Viper 是 Go 應(yīng)用程序現(xiàn)代化的、完整的解決方案,能夠處理不同格式的配置文件 Viper 可以從不同的位置讀取配置,不同位置的配置具有不同的優(yōu)先級:
- 通過 viper.Set 函數(shù)顯示設(shè)置的配置
- 命令行參數(shù)
- 環(huán)境變量
- 配置文件
- Key/Value 存儲
- 默認(rèn)值
3.1讀入配置
讀入配置,就是將配置讀入到 Viper 中,有如下讀入方式:
- 設(shè)置默認(rèn)值。
// 當(dāng)沒有通過配置文件、環(huán)境變量、遠(yuǎn)程配置或命令行標(biāo)志設(shè)置 key 時,設(shè)置默認(rèn)值通常是很有用的 viper.SetDefault("ContentDir", "content") viper.SetDefault("LayoutDir", "layouts") viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
- 讀取配置文件
package main import ( "fmt" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "Configuration file.") help = pflag.BoolP("help", "h", false, "Show this help message.") ) func main() { pflag.Parse() if *help { pflag.Usage() return } // 從配置文件中讀取配置 if *cfg != "" { viper.SetConfigFile(*cfg) // 指定配置文件名 viper.SetConfigType("yaml") // 如果配置文件名中沒有文件擴(kuò)展名,則需要指定配置文件的格式,告訴viper以何種格式解析文件 } else { viper.AddConfigPath(".") // 把當(dāng)前目錄加入到配置文件的搜索路徑中 viper.AddConfigPath("$HOME/.iam") // 配置文件搜索路徑,可以設(shè)置多個配置文件搜索路徑 viper.SetConfigName("config") // 配置文件名稱(沒有文件擴(kuò)展名) } if err := viper.ReadInConfig(); err != nil { // 讀取配置文件。如果指定了配置文件名,則使用指定的配置文件,否則在注冊的搜索路徑中搜索 panic(fmt.Errorf("Fatal error config file: %s \n", err)) } fmt.Printf("Used configuration file is: %s\n", viper.ConfigFileUsed()) }
- 監(jiān)聽和重新讀取配置文件。 Viper 支持在運(yùn)行時讓應(yīng)用程序?qū)崟r讀取配置文件,也就是熱加載配置
viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { // 配置文件發(fā)生變更之后會調(diào)用的回調(diào)函數(shù) fmt.Println("Config file changed:", e.Name) }) //不建議在實(shí)際開發(fā)中使用熱加載功能,因?yàn)榧词古渲脽峒虞d了,程序中的代碼也不一定會熱加載。例如:修改了服務(wù)監(jiān)聽端口,但是服務(wù)沒有重啟,這時候服務(wù)還是監(jiān)聽在老的端口上,會造成不一致
- 設(shè)置配置值
// 可以通過 viper.Set() 函數(shù)來顯式設(shè)置配置: viper.Set("user.username", "colin")
- 使用環(huán)境變量
// 使用環(huán)境變量 os.Setenv("VIPER_USER_SECRET_ID", "QLdywI2MrmDVjSSv6e95weNRvmteRjfKAuNV") os.Setenv("VIPER_USER_SECRET_KEY", "bVix2WBv0VPfrDrvlLWrhEdzjLpPCNYb") viper.AutomaticEnv() // 讀取環(huán)境變量 viper.SetEnvPrefix("VIPER") // 設(shè)置環(huán)境變量前綴:VIPER_,如果是viper,將自動轉(zhuǎn)變?yōu)榇髮憽? viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) // 將viper.Get(key) key字符串中'.'和'-'替換為'_' viper.BindEnv("user.secret-key") viper.BindEnv("user.secret-id", "USER_SECRET_ID") // 綁定環(huán)境變量名到key
- 使用標(biāo)志 Viper 支持 Pflag 包,能夠綁定 key 到 Flag。與 BindEnv 類似,在調(diào)用綁定方法時,不會設(shè)置該值,但在訪問它時會設(shè)置。
viper.BindPFlag("token", pflag.Lookup("token")) // 綁定單個標(biāo)志 viper.BindPFlags(pflag.CommandLine) //綁定標(biāo)志集
3.2 讀取配置
- 訪問嵌套的鍵。
{ "host": { "address": "localhost", "port": 5799 }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } } viper.GetString("datastore.metric.host") // (返回 "127.0.0.1")
- 反序列化 Viper 可以支持將所有或特定的值解析到結(jié)構(gòu)體、map 等??梢酝ㄟ^兩個函數(shù)來實(shí)現(xiàn):
type config struct { Port int Name string PathMap string `mapstructure:"path_map"` } var C config err := viper.Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) }
Viper 在后臺使用github.com/mitchellh/mapstructure來解析值,其默認(rèn)情況下使用mapstructure tags。當(dāng)我們需要將 Viper 讀取的配置反序列到我們定義的結(jié)構(gòu)體變量中時,一定要使用 mapstructure tags
- 序列化成字符串
import ( yaml "gopkg.in/yaml.v2" // ... ) func yamlStringSettings() string { c := viper.AllSettings() bs, err := yaml.Marshal(c) if err != nil { log.Fatalf("unable to marshal config to YAML: %v", err) } return string(bs) }
4.現(xiàn)代化的命令行框架:Cobra
Cobra 既是一個可以創(chuàng)建強(qiáng)大的現(xiàn)代 CLI 應(yīng)用程序的庫,也是一個可以生成應(yīng)用和命令文件的程序 應(yīng)用程序通常遵循如下模式:APPNAME VERB NOUN --ADJECTIVE或者APPNAME COMMAND ARG --FLAG VERB 代表動詞,NOUN 代表名詞,ADJECTIVE 代表形容詞
4.1 使用 Cobra 庫創(chuàng)建命令
- 創(chuàng)建 rootCmd
$ mkdir -p newApp2 && cd newApp2 // 通常情況下,我們會將 rootCmd 放在文件 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ù)中定義標(biāo)志和處理配置 import ( "fmt" "os" homedir "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) var ( cfgFile string projectBase string userLicense string ) func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/") rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution") rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)") rootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration") viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) viper.BindPFlag("projectbase", rootCmd.PersistentFlags().Lookup("projectbase")) viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper")) viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>") viper.SetDefault("license", "apache") } func initConfig() { // Don't forget to read config either from cfgFile or from home directory! if cfgFile != "" { // Use config file from the flag. viper.SetConfigFile(cfgFile) } else { // Find home directory. home, err := homedir.Dir() if err != nil { fmt.Println(err) os.Exit(1) } // Search config in home directory with name ".cobra" (without extension). viper.AddConfigPath(home) viper.SetConfigName(".cobra") } if err := viper.ReadInConfig(); err != nil { fmt.Println("Can't read config:", err) os.Exit(1) } }
- 創(chuàng)建 main.go 我們還需要一個 main 函數(shù)來調(diào)用 rootCmd,通常我們會創(chuàng)建一個 main.go 文件,在 main.go 中調(diào)用 rootCmd.Execute() 來執(zhí)行命令
package main import ( "{pathToYourApp}/cmd" ) func main() { cmd.Execute() }
- 添加命令 通常情況下,我們會把其他命令的源碼文件放在 cmd/ 目錄下,例如,我們添加一個 version 命令,可以創(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") }, }
- 編譯并運(yùn)行
$ go mod init github.com/marmotedu/gopractise-demo/cobra/newApp2 $ go build -v . $ ./newApp2 -h 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 Usage: hugo [flags] hugo [command] Available Commands: help Help about any command version Print the version number of Hugo 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 hugo -l, --license licensetext Name of license for the project (can provide licensetext in config) -b, --projectbase string base project directory eg. github.com/spf13/ --viper Use Viper for configuration (default true) Use "hugo [command] --help" for more information about a command.
4.2使用標(biāo)志
Cobra 可以跟 Pflag 結(jié)合使用,實(shí)現(xiàn)強(qiáng)大的標(biāo)志功能
// 使用持久化的標(biāo)志 rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") // 分配一個本地標(biāo)志,本地標(biāo)志只能在它所綁定的命令上使用 rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") // 將標(biāo)志綁定到 Viper var author string func init() { rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution") viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) } // 設(shè)置標(biāo)志為必選 rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)") rootCmd.MarkFlagRequired("region")
5.總結(jié)
在開發(fā) Go 項(xiàng)目時,我們可以通過 Pflag 來解析命令行參數(shù),通過 Viper 來解析配置文件,用 Cobra 來實(shí)現(xiàn)命令行框架。
你可以通過 pflag.String()、 pflag.StringP()、pflag.StringVar()、pflag.StringVarP() 方法來設(shè)置命令行參數(shù),并使用 Get 來獲取參數(shù)的值
以上就是go語言Pflag Viper Cobra 核心功能使用介紹的詳細(xì)內(nèi)容,更多關(guān)于Go語言Pflag Viper Cobra功能的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決Go?Json?Unmarshal反序列化丟失數(shù)字精度問題
業(yè)務(wù)會使用?id生成器?產(chǎn)生的?分布式唯一ID,長度比較長,所以代碼反序列化時,會出現(xiàn)精度丟失問題,那如何解決呢,下面小編就來和大家詳細(xì)講講2023-08-08分析Go錯誤處理優(yōu)化go?recover機(jī)制缺陷
這篇文章主要為大家介紹了分析Go錯誤處理優(yōu)化go?recover機(jī)制缺陷示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07GoLang string與strings.Builder使用對比詳解
這篇文章主要介紹了GoLang string與strings.Builder使用對比,Builder 用于使用 Write 方法有效地構(gòu)建字符串。它最大限度地減少了內(nèi)存復(fù)制。零值可以使用了。不要復(fù)制非零生成器2023-03-03golang常用加密解密算法總結(jié)(AES、DES、RSA、Sha1、MD5)
在項(xiàng)目開發(fā)過程中,當(dāng)操作一些用戶的隱私信息,本文主要主要介紹了golang常用加密解密算法總結(jié)(AES、DES、RSA、Sha1MD5),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04GO使用socket和channel實(shí)現(xiàn)簡單控制臺聊天室
今天小編給大家分享一個簡單的聊天室功能,聊天室主要功能是用戶可以加入離開聊天室,實(shí)現(xiàn)思路也很簡單明了,下面小編給大家?guī)砹送暾a,感興趣的朋友跟隨小編一起看看吧2021-12-12golang實(shí)現(xiàn)簡單工廠、方法工廠、抽象工廠三種設(shè)計(jì)模式
這篇文章介紹了golang實(shí)現(xiàn)簡單工廠、方法工廠、抽象工廠三種設(shè)計(jì)模式的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-04-04