0%

go命令行解析

go常用命令行包使用汇总 flag, pflag, viper, corba

flag用法

flag使用几种方式

  1. flag.Xxx, 其中 Xxx 可以是 Int、String 等;返回一个相应类型的指针
  2. flag.XxxVar,其中 XxxVar 可以是 Int、String 等;将 flag 绑定到一个变量上
  3. 自定义类型flag.Var解析. 自定义类型需要实现flag.Value接口中的方法 Set 和String
  4. flagset生成子命令

//解析 flag
通过以上几种方法定义好命令行flag参数后,需要通过调用flag.Parse()来对命令行参数进行解析。

支持的命令行参数格式有以下几种:

-flag xxx (使用空格,一个-符号)
–flag xxx (使用空格,两个-符号)
-flag=xxx (使用等号,一个-符号)
–flag=xxx (使用等号,两个-符号)
其中,布尔类型的参数必须使用等号的方式指定。bool类型如果flag出现就为true,否则为默认值,只能使用=号连接
Flag解析在第一个非flag参数(单个”-“不是flag参数)之前停止,或者在终止符”–“之后停止。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package main

import (
"flag"
"fmt"
"strings"
"time"
)

/**
flag使用几种方式
1. flag.Xxx, 其中 Xxx 可以是 Int、String 等;返回一个相应类型的指针
2. flag.XxxVar,其中 XxxVar 可以是 Int、String 等;将 flag 绑定到一个变量上
3. 自定义类型flag.Var解析. 自定义类型需要实现flag.Value接口中的方法 Set 和String
4. flagset生成子命令
*/
type interval []time.Duration

func (i *interval) String() string {
return fmt.Sprint(*i)
}

func (i *interval) Set(value string) error {
// 此部分如果不注释,就不允许同一个参数定义多次,-interval 30m -interval 10m 是不允许。
//if len(*i) > 0 {
// return errors.New("interval flag already set")
//}
for _, dt := range strings.Split(value, ",") {
duration, err := time.ParseDuration(dt)
if err != nil {
return err
}
*i = append(*i, duration)
}
return nil

}

func main() {
//flag.Xxx(),其中 Xxx 可以是 Int、String 等;返回一个相应类型的指针
wordPtr := flag.String("word", "test", "word")
durtion := flag.Duration("duration", time.Minute, "set time duration")
//flag.XxxVar(),其中 XxxVar 可以是 Int、String 等;将 flag 绑定到一个变量上
var svar string
flag.StringVar(&svar, "svar", "bar", "a string var")
//flag.Var解析自定义类型
var i interval
flag.Var(&i, "interval", "解析url")



flag.Parse()
fmt.Println("word:", *wordPtr)

fmt.Println("stringval:", svar)
fmt.Println("durtion:", *durtion)
fmt.Println("custom:", i)
}
/**
# flag.exe -word word -svar hello -duration 2h -interval=1m,2m,3m -interval=1h
word: word
stringval: hello
durtion: 2h0m0s
custom: [1m0s 2m0s 3m0s 1h0m0s]
*/


flagset 子命令方式

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
27
28
29
30
package main

import (
"flag"
"fmt"
"os"
)

func main() {
sub := flag.NewFlagSet("sub1", flag.ExitOnError)
intervals := sub.String("interval", "1m", "sub interval")
nums := sub.Int("num", 3, "sub nums")
//bool 类型不使用=号就无法解析
bools := sub.Bool("bool", true, "sub nums")

sub.Parse(os.Args[2:])
fmt.Println(*intervals)
fmt.Println(*nums)
fmt.Println(*bools)

}

/*
# sumcommand.exe sub1 -num 30 --interval=23 -bool false
23
30
true

*/

pflag用法

pflag是flag的增强版,兼容flag,同时有自己新增的一些特性。

pflag使用几种方式

  1. pflag. 类方法名会将标志参数值存储在指针中并返回。
  2. pflag.Var 类方法名中包含 Var 关键字的,会将标志参数值绑定到第一个指针类型的参数。
  3. pflag.P、pflag.VarP 类方法名以 P 结尾的,支持简短标志。
    一个完整标志在命令行传参时使用的分界符为 –,而一个简短标志的分界符则为 -。

pflag中flag定义以及Value接口定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Flag struct {
Name string // flag长选项的名称
Shorthand string // flag短选项的名称,一个缩写的字符
Usage string // flag的使用文本
Value Value // flag的值
DefValue string // flag的默认值
Changed bool // 记录flag的值是否有被设置过
NoOptDefVal string // 当flag出现在命令行,但是没有指定选项值时的默认值
Deprecated string // 记录该flag是否被放弃
Hidden bool // 如果值为true,则从help/usage输出信息中隐藏该flag
ShorthandDeprecated string // 如果flag的短选项被废弃,当使用flag的短选项时打印该信息
Annotations map[string][]string // 给flag设置注解
}
//不同于官方的Value接口,新增了一个Type方法
type Value interface {
String() string // 将flag类型的值转换为string类型的值,并返回string的内容
Set(string) error // 将string类型的值转换为flag类型的值,转换失败报错
Type() string // 返回flag的类型,例如:string、int、ip等
}

pflag应用代码

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package main

import (
"fmt"
"strings"
"time"

"github.com/spf13/pflag"
)

type intervals []time.Duration

func (i *intervals) String() string {
return fmt.Sprint(*i)
}

func (i *intervals) Set(value string) error {
for _, dt := range strings.Split(value, ",") {
duration, err := time.ParseDuration(dt)
if err != nil {
return err
}
*i = append(*i, duration)
}
return nil
}

func (i *intervals) Type() string {
return "intervals"
}

func main() {

pflagxxx := pflag.String("pflagxxx", "pflagxxx", "string for args to return a string pointer")
var pflagxxxVar string
pflag.StringVar(&pflagxxxVar, "pflagxxxVar", "pflagxxxVar", "stringvar for args to var")

pflagxxxP := pflag.StringP("pflagxxxP", "t", "pflagxxxP", "string for args to return a string pointer with shorthand")

var pflagxxxVarP string
pflag.StringVarP(&pflagxxxVarP, "pflagxxxVarP", "p", "pflagxxxVarP", "stringvar for args to var with shorthand")

var in intervals
pflag.Var(&in, "interval", "间隔")
var inp intervals
pflag.VarP(&inp, "intervalp", "i", "简短标志间隔")

//如果标志具有 NoOptDefVal 属性并且在命令行上设置了标志而没有参数选项,则标志将设置为 NoOptDefVal 指定的值。
pflag.Lookup("interval").NoOptDefVal = "2m"

//作用是禁止打印帮助信息时对标志进行重排序。
pflag.CommandLine.SortFlags = false

//命令行解析
pflag.Parse()

fmt.Println(*pflagxxx)
fmt.Println(pflagxxxVar)
fmt.Println(*pflagxxxP)
fmt.Println(pflagxxxVarP)
fmt.Println(in)
fmt.Println(inp)
fmt.Printf("NFlag: %v\n", pflag.NFlag()) // 返回已设置的命令行标志个数
fmt.Printf("NArg: %v\n", pflag.NArg()) // 返回处理完标志后剩余的参数个数
fmt.Printf("Args: %v\n", pflag.Args()) // 返回处理完标志后剩余的参数列表
fmt.Printf("Arg(1): %v\n", pflag.Arg(1)) // 返回处理完标志后剩余的参数列表中第 i 项
}

/*
>pflag.exe -h
Usage of pflag.exe:
--pflagxxx string string for args to return a string pointer (default "pflagxxx")
--pflagxxxVar string stringvar for args to var (default "pflagxxxVar")
-t, --pflagxxxP string string for args to return a string pointer with shorthand (default "pflagxxxP")
-p, --pflagxxxVarP string stringvar for args to var with shorthand (default "pflagxxxVarP")
--interval intervals[=2m] 间隔 (default [])
-i, --intervalp intervals 简短标志间隔 (default [])
pflag: help requested


>pflag.exe --pflagxxx "a" --pflagxxxVar "b" -t "c" -p "d" --interval -i 4m,5m ada badc
a
b
c
d
[2m0s]
[4m0s 5m0s]
argument number is: 2
argument list is: [ada badc]
the first argument is: ada


*/

viper用法

Viper是Go应用程序现代化的、完整的解决方案,能够处理不同格式的配置文件,让我们在构建现代应用程序时,不必担心配置文件格式。Viper也能够满足我们对应用配置的各种需求。

Viper可以从不同的位置读取配置,不同位置的配置具有不同的优先级,高优先级的配置会覆盖低优先级相同的配置,按优先级从高到低排列如下:

  • 通过viper.Set函数显示设置的配置
  • 命令行参数
  • 环境变量
  • 配置文件
  • Key/Value存储
  • 默认值
  • 这里需要注意,Viper配置键不区分大小写。

Viper有很多功能,最重要的两类功能是读入配置和读取配置。
读入配置
读入配置,就是将配置读入到Viper中,有如下读入方式:

  • 设置默认的配置文件名。
  • 读取配置文件。
  • 监听和重新读取配置文件。
  • 从io.Reader读取配置。
  • 从环境变量读取。
  • 从命令行标志读取。
  • 从远程Key/Value存储读取。
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112

package main

import (
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/pflag"
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
"io/ioutil"
"time"
)

var (
confMD5 string
username = pflag.StringP("username", "u", "pflag-username", "help message for username")
)

func main() {
pflag.Parse()
//优先级高到低
//1. viper.Set 优先级最高
//viper.Set("projectbase", "highPro") 6
//2. 从命令行获取配置 5
//viper.BindPFlag("projectbase", pflag.Lookup("username"))
//3. 绑定环境变量 4
//viper.BindEnv("projectbase", "projectbase")
//4. 配置文件写入 3
//5. key/value存储 远程 2
viper.AddRemoteProvider("consul", "172.20.6.37:8500", "user") // 连接远程 consul 服务
viper.SetConfigType("YAML") // 显式设置文件格式文 YAML
if err := viper.ReadRemoteConfig(); err != nil {
fmt.Printf("-=============%s", err.Error())
}

//6. 设置默认值 1
viper.SetDefault("projectbase", "prevalue")

fmt.Printf("+++++%v\n", viper.Get("projectbase"))
//配置写入
//viper.SetConfigFile("top.yaml")
//if err := viper.ReadInConfig(); err != nil {
// fmt.Errorf("%s", err.Error())
//}

//热加载,解决多次调用OnConfigChange问题
viper.OnConfigChange(func(e fsnotify.Event) {
tconfMD5, _ := ReadFileMd5("top.yaml")
fmt.Printf("[%+v]\n", tconfMD5)
if tconfMD5 == confMD5 {
return
}
confMD5 = tconfMD5
fmt.Println("cfg start....", e.String(), e.Name)

})

// 监控并重新读取配置文件,需要确保在调用前添加了所有的配置路径
viper.WatchConfig()

//反序列化 常用
var topcfg TopCfg
err := viper.Unmarshal(&topcfg)
if err != nil {
return
}
fmt.Printf("%+v\n", topcfg)
fmt.Printf("%#v\n===", topcfg)

//从viper中读取配置
if keys := viper.AllKeys(); len(keys) > 0 {
for _, key := range keys {
fmt.Printf("%s:%v\n", key, viper.Get(key))

}
}
//提取字树
grpcCfg := viper.Sub("grpc")
fmt.Printf("%v\n", grpcCfg.AllKeys())
fmt.Printf("%v\n", grpcCfg.Get("username"))
//访问嵌套的键
fmt.Printf("%v\n", viper.Get("grpc.username"))

//序列化
viper.WriteConfigAs("new.yaml")
for {
time.Sleep(3 * time.Second)
fmt.Printf("%v\n", viper.Get("projectbase"))
}
}




func ReadFileMd5(sfile string) (string, error) {
ssconfig, err := ioutil.ReadFile(sfile)
if err != nil {
return "", err
}
return GetMD5(ssconfig), nil
}

func GetMD5(s []byte) string {
m := md5.New()
m.Write([]byte(s))
return hex.EncodeToString(m.Sum(nil))
}



corba用法

Cobra 是一个 Go 语言开发的命令行(CLI)框架,它提供了简洁、灵活且强大的方式来创建命令行程序。它包含一个用于创建命令行程序的库(Cobra 库),以及一个用于快速生成基于 Cobra 库的命令行程序工具(Cobra 命令)。Cobra 是由 Go 团队成员 spf13 为 Hugo 项目创建的,并已被许多流行的 Go 项目所采用,如 Kubernetes、Helm、Docker (distribution)、Etcd 等。

参考

Go - flag:命令行flags解析
Go 命令行参数解析工具 pflag 使用
在 Go 中如何使用 Viper 来管理配置
Go 语言现代命令行框架 Cobra 详解