os.Args os包中提供命令行接收后续参数,但是使用比较简单。
缺点是无法解析参数,只能通过位置值获取,内置的flag包则可以实现命令行参数的解析
1 2 3 4 5 6 7 8 9 for i := range os.Args { fmt.Println(os.Args[i]) } go run main.go 1 2 3 4 1 2 3 4
flag flag包支持的命令行参数类型:bool int int64 uint uint64 float64 string duration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 b := flag.Bool("bool", false, "bool类型") i := flag.Int("int", 0, "int类型") i64 := flag.Int64("int64", 0, "int64类型") u := flag.Uint("uint", 0, "uint类型") u64 := flag.Uint64("uint64", 0, "uint64类型") f64 := flag.Float64("float64", 0.0, "float64类型") s := flag.String("string", "", "string类型") d := flag.Duration("duration", time.Second, "时间类型") // 语法分析 flag.Parse() // 也可以类似os.Args接收参数 fmt.Printf("所有参数:%v,参数个数:%d\n", flag.Args(), flag.NArg()) for i := 0; i < flag.NArg(); i++ { fmt.Printf("第%d个参数,值是%v\n", i, flag.Arg(i)) } fmt.Printf("bool: %t,int: %d,int64: %d,uint: %d,uint64: %d,float64: %f,string: %s,duration: %v\n", *b, *i, *i64, *u, *u64, *f64, *s, *d)
输出
1 2 3 4 5 go run main.go --int64=64 --uint64=164 --bool=true --int=123 --uint=2 --float64=1.7 --string=a --duration=10s arg1 所有参数:[arg1],参数个数:1 第0个参数,值是arg1 bool: true,int: 123,int64: 64,uint: 2,uint64: 164,float64: 1.700000,string: a,duration: 10s
viper 适用于Go应用程序的完整配置解决方案。(不全面,这里列举几个经常会用到的特性,更全面的特性可以查看官方RM文档):
设置默认配置
从JSON,YAML格式的配置文件读取配置信息
从环境变量中读取
从命令行参数读取
显示配置值
安装
1 go get github.com/spf13/viper
参数设置优先级,从高到低:
显示调用Set设置值
命令行参数(flag)
环境变量
配置文件
Key/value存储
默认值
viper的用法是将配置存入viper,使用时,从viper读出来
1 2 viper.Set("service", "viper") viper.Get("service") // 返回viper
将值存入viper 设置值
1 viper.Set("service", "viper")
默认值
1 viper.SetDefault("service", "abc")
通过配置文件
viper可以从多个目录搜索各种格式的配置文件
1 2 3 4 5 6 7 8 viper.SetConfigFile("./config.yaml") // 直接指定 viper.SetConfigName("config") // 配置文件名称(无扩展名) viper.SetConfigType("yaml") // 如果配置文件的名称中没有扩展名,则需要配置此项 viper.AddConfigPath("/etc/xxx/") // 查找配置文件所在的路径 viper.AddConfigPath(".") // 多次调用以添加多个搜索路径 err := viper.ReadInConfig() // 查找并读取配置文件,返回的报错可能是没找到,也可能是找到了但是有其他的错误
监控并重新读取配置文件
Viper支持在运行时实时读取配置文件的功能。通过回调函数,实现改动之后的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 viper.SetConfigFile("./config.yaml") go func() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { // 配置文件发生变更之后会调用的回调函数 fmt.Println("Config file changed:", e.Name, e.Op) err := viper.ReadInConfig() if err != nil { log.Fatal(err) } fmt.Println(viper.Get("service")) }) }()
改动之后
1 2 Config file changed: config.yaml WRITE|CHMOD viper12345
从io.Reader读取配置
viper可以通过读取的字符串中的获取配置
1 2 3 4 5 6 7 8 9 10 11 f, err := os.Open("./config.yaml") if err != nil { log.Fatal(err) } defer f.Close() viper.SetConfigType("yaml") err = viper.ReadConfig(f) if err != nil { log.Fatal(err) } fmt.Println(viper.Get("service"))
别名
别名允许多个键引用一个值
1 2 3 viper.RegisterAlias("service", "anotherService") viper.Set("service", "viper") fmt.Println(viper.Get("anotherService"))
使用环境变量
1 2 3 4 5 6 7 SetEnvPrefix(string) 设置环境变量前缀 SetEnvKeyReplacer(string...) *strings.Replacer 环境变量字符串变更 AutomaticEnv() 在get时,自动获取对应的环境变量 BindEnv(string...) : error 绑定环境变量的值到viper中 AllowEmptyEnv(bool) 空环境变量,默认为false
1 2 3 4 5 6 7 8 9 viper.AutomaticEnv() // 自动绑定 viper.SetEnvPrefix("vip") os.Setenv("VIP_ID", "13") fmt.Println(viper.Get("id")) // 自动绑定之后获取 // 13 replacer := strings.NewReplacer("VIP", "NVIP") // 将获取的前缀从VIP改成NVIP viper.SetEnvKeyReplacer(replacer) os.Setenv("NVIP_ID", "14") viper.BindEnv("id") fmt.Println(viper.Get("id")) // 14
使用Pflag
类似于falg
,在命令行中通过参数传递。例如在Cobra
库中使用Pflag
1 2 3 4 5 6 7 8 9 10 11 12 13 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 }, } rootCmd.Flags().Int("port", 1138, "Port to run Application server on") // 当如果没有参数传入,使用默认值1138 viper.BindPFlag("port", rootCmd.Flags().Lookup("port")) fmt.Println(viper.Get("port"))
或者直接使用pflag
1 2 3 4 pflag.String("service", "viper", "this is service name") pflag.Parse() viper.BindPFlags(pflag.CommandLine) fmt.Println(viper.Get("service"))
也可以自己实现flag
接口,具体实现方式在详细说明文档中。
远程Key/Value
存储
在Viper中启用远程支持,需要在代码中匿名导入viper/remote
这个包。
1 import _ "github.com/spf13/viper/remote"
Viper
可以从etcd
或Consul
中读取配置,并且设置加密。在etcd
中可以监控配置值是否被修改。
将配置写入文件 viper支持当配置改动之后,将配置写入到文件,可以安全的写入,也就是不覆盖,只新建
1 2 3 4 5 6 7 8 viper.AddConfigPath("./") viper.SetConfigName("config2") viper.SetConfigType("yaml") viper.WriteConfig() // 将当前配置写入“viper.AddConfigPath()”和“viper.SetConfigName”设置的预定义路径 viper.SafeWriteConfig() viper.WriteConfigAs("config.yaml") viper.SafeWriteConfigAs("./config") // 因为该配置文件写入过,所以会报错 Config File "xxx/config2.yaml" Already Exists
从Viper中获取值 可以通过值的类型获取值
1 2 3 4 5 6 7 8 9 10 11 12 13 Get(key string) : interface{} GetBool(key string) : bool GetFloat64(key string) : float64 GetInt(key string) : int GetIntSlice(key string) : []int GetString(key string) : string GetStringMap(key string) : map[string]interface{} GetStringMapString(key string) : map[string]string GetStringSlice(key string) : []string GetTime(key string) : time.Time GetDuration(key string) : time.Duration IsSet(key string) : bool AllSettings() : map[string]interface{}
每一个Get方法在找不到值的时候都会返回零值。为了检查给定的键是否存在,提供了IsSet()
方法。
1 2 3 4 viper.Set("service", "viper") if viper.IsSet("service") { fmt.Println(viper.Get("service")) }
嵌套数据访问
1 2 viper.Set("service.name", "viper") fmt.Println(viper.Get("service")) // map[name:viper]
这遵守上面建立的优先规则;搜索路径将遍历其余配置注册表,直到找到为止。(译注:因为Viper支持从多种配置来源,例如磁盘上的配置文件>命令行标志位>环境变量>远程Key/Value存储>默认值,我们在查找一个配置的时候如果在当前配置源中没找到,就会继续从后续的配置源查找,直到找到为止。)
例如,在给定此配置文件的情况下,datastore.metric.host
和datastore.metric.port
均已定义(并且可以被覆盖)。如果另外在默认值中定义了datastore.metric.protocol
,Viper也会找到它。
然而,如果datastore.metric
被直接赋值覆盖(被flag,环境变量,set()
方法等等…),那么datastore.metric
的所有子键都将变为未定义状态,它们被高优先级配置级别“遮蔽”(shadowed)了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "datastore.metric.host": "0.0.0.0", "host": { "address": "localhost", "port": 5799 }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } } GetString("datastore.metric.host") // 返回 "0.0.0.0"
反序列化
viper
支持直接将配置反序列化到结构体对象中
1 2 Unmarshal(rawVal interface{}) : error UnmarshalKey(key string, rawVal interface{}) : error
1 2 3 4 5 6 7 8 9 10 11 12 viper.Set("service.name" , "viper" ) var s = struct { Service struct { Name1 string `mapstructure:"name"` } }{} err := viper.Unmarshal(&s) if err != nil { log.Fatalln(err) } fmt.Printf("%+v\n" , s)
输出
将所有的配置序列化成字符串,后续可以反序列化成不同的格式
1 2 3 4 5 6 viper.Set("service.name", "viper") viper.Set("service.type", "test") s := viper.AllSettings() fmt.Printf("%+v\n", s) bytes, _ := yaml.Marshal(s) fmt.Println(string(bytes))
输出
1 2 3 4 map[service:map[name:viper type:test]] service: name: viper type: test
用法推荐 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 pflag.String("service.name", "viper", "this is service name") // 设置pflag默认值 // 如果没有指定,就使用配置文件中默认配置 var configFile string pflag.StringVar(&configFile, "configFile", "./config.yaml", "server config file") pflag.Parse() // 命令行参数也可以传配置文件路径 // 绑定配置文件 if configFile != "" { viper.SetConfigFile(configFile) err := viper.ReadInConfig() if err != nil && !os.IsNotExist(err) { fmt.Println("config file: ", err) } } // 绑定cmd参数 _ = viper.BindPFlags(pflag.CommandLine) // 绑定环境变量 viper.SetEnvPrefix("viper") viper.AutomaticEnv() viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // 将.替换成_解析,VIPER.xxx和VIPER_xxx都可以解析 // 解析绑定的参数 common.Service.Name = viper.GetString("service.name")
同时支持命令行参数、环境变量、配置文件,参数优先级是命令行大于环境变量大于配置文件。
推荐阅读 推荐阅读中的文档更加详细:
官方文档 更详细文档:李文周的博客