
标准库flag包
1.命令行
写命令行程序时需要对命令参数进行解析,这时可以使用标准库的os包可以通过变量Args来获取命令参数,os.Args返回一个字符串数组,其中第一个参数就是执行文件本身。例如:
package main import ( "fmt" "os" ) func main() { fmt.Println(os.Args) for k, v := range os.Args { fmt.Println(k, ":", v) } }Args参数:
$./00 config=".\config\server.conf" [./00 config=.\config\server.conf] 0 : ./00 1 : config=.\config\server.conf利用os.Args能得到需要的参数值,对于复杂的参数格式,这时需要选择标准库的flag包
2.参数解析
flag包是GO语言标准库提供的用来解析命令行参数的包,让开发命令行工具更为简单
flag包的用法:
flag包支持的标志格式有:
-flag 代表bool值,相当于-flag=true -flag=x 支持所有的值 -flag x 不支持bool值标志命令行参数是指运行程序体提供的参数,非flag(non-flag)命令行参数(或保留的命令行参数)可以简单理解为flag包不能解析的参数。例如
package main import ( "flag" "fmt" "os" ) var ( h, H bool v bool q *bool D string Conf string ) func init() { flag.BoolVar(&h, "h", false, "帮助信息") flag.BoolVar(&h, "H", false, "帮助信息") flag.BoolVar(&v, "v", false, "显示版本号") // flag.StringVar(&D, "D", "deamon", "set descripton ") flag.StringVar(&Conf, "Conf", "/dev/conf/cli.conf", "set Conf filename ") // 另一种绑定方式 q = flag.Bool("q", false, "退出程序") // 像flag.Xxx函数格式都是一样的,第一个参数表示参数名称, // 第二个参数表示默认值,第三个参数表示使用说明和描述。 // flag.XxxVar这样的函数第一个参数换成了变量地址, // 后面的参数和flag.Xxx是一样的。 // 改变默认的 Usage flag.Usage = usage flag.Parse() var cmd string = flag.Arg(0) fmt.Printf("-----------------------\n") fmt.Printf("cli non=flags : %s\n", cmd) fmt.Printf("q: %t\n", *q) fmt.Printf("descripton: %s\n", D) fmt.Printf("Conf filename : %s\n", Conf) fmt.Printf("-----------------------\n") fmt.Printf("there are %d non-flag input param\n", flag.NArg()) for i, param := range flag.Args() { fmt.Printf("#%d :%s\n", i, param) } } func main() { flag.Parse() if h || H { flag.Usage() } } func usage() { fmt.Fprintf(os.Stderr, `CLI: 8.0 Usage: Cli [-hvq] [-D descripton] [-Conf filename] `) flag.PrintDefaults() }编译后执行:
$./01 -D="host" -Conf="/dev/cli.conf" ----------------------- cli non=flags : q: false descripton: host Conf filename : /dev/cli.conf ----------------------- there are 0 non-flag input param命令行的实现基本还是上面那几步骤。
3. flag包实现命令行参数解析
flag包实现命令行参数的解析,大致需要以下几个步骤
1. flag参数定义或绑定
定义flags有两种方式:
(1)flag.Xxx(),其中Xxx可以是int、String等,返回一个相应类型的指针,如:
var ip = flag.Int("flagname",1234,"help message for flagname")(2)flag.XxxVar(),将flag绑定到一个变量上,如:
var flagvar int flag.IntVar(&flagvar,"flagname",1234,"help message for flagname")另外,还可以创建自定义flag,只要实现flag.Value接口即可(要求receiver是指针)。这时候可以通过如下方式定义该flag.
flag.Var(&flag val,"name","help message for flagname")2.flag参数解析
在所有的flag定义完成之后,可以通过调用flag.Parse()进行解析。
根据Parse()中for循环终止的条件,当parseOne()方法返回false,nil时,Parse解析终止。
s := f.args[0] if len(s) == 0 || s[0] != '-' || len(s) == 1{ return false,nil }当遇到单独的一个
“-”或不是“-”开始的参数时,会停止解析。例如:./cli - -f或./cli -f这两种情况,
-f都不会被正确解析。这些参数成为non-flag参数parseOne方法中接下来是处理
-flag=x,然后是-flag(bool类型),这里对bool进行了特殊处理,接着是-flag x这种形式,最后将解析成功的Flag实例存入FlagSet的actual字段中。Arg(i int)和Args()这两个方法就是获取non-flag参数的。NArg()获得non-flag的个数。NFlag()获得FlagSet中actual的长度(既被设置的参数个数)
flag解析遇到non-flag参数就停止了,所以如果将non-flag参数放在最前面,flag什么也不会解析,因为flag遇到non-flag参数就停止解析了。
3.分支程序
根据参数值,代码进入分支程序,执行相关功能,下面的代码提供了
-h参数的功能。if h||H{ flagUsage() }总体而言,falg包咋处理命令行参数时是很有用的。但是在遇到更加复杂和参数多样化的命令行程序(如go命令),实现起来会比较费劲,这时候可以考虑更方便的实现方式
建议使用Cobra包,它是一个用来创建强大的现代CLI命令行程序的Go语言开源第三方包,它一方面比较适合构建复杂的命令行程序,另外一方面容易上手,如果要开发更加复杂的命令行程序,建议使用这个包https://github.com/spf13/cobra
4.命令行参数
var n = flag.Bool("n", false, "omit trailing newline") var sep = flag.String("s", " ", "separator") func main() { flag.Parse() fmt.Printf(strings.Join(flag.Args(), *sep)) if !*n { fmt.Println() } }标识参数:
-n使程序忽略正常输出时结尾的换行符,-ssep使用替换默认参数输出时使用的空格分割符[administrator@administrator-dev 20210312]$ ./04 a bc def a bc def [administrator@administrator-dev 20210312]$ ./04 -s / a bc def a/bc/def [administrator@administrator-dev 20210312]$ ./04 -n a bc def a bc def[administrator@administrator-dev 20210312]$ ./04 -help Usage of ./04: -n omit trailing newline -s string separator (default " ")flag.Bool函数创建一个新的布尔标识变量,它有三个参数:标识的名字("n").变量的默认值alse,以及当用户提供非法标识,非法参数,-h -help参数时输出的消息.同样的flag.String也使用名字,默认值和消息来创建一个字符串变量,变量sep和n是指向变量的指针,它们必须通过sep和n来访问.当程序运行时,在使用标识前,必须调用flag.Parse来更新标识变量的默认值.非标识参数也可以从flag.Args()返回的字符串slice来访问,如果flag.Parse遇到错误,它输出一条帮助消息,然后调用os.Exit(2)来结束程序.
5.flag.Parse
当程序运行时,在使用标识前,必须调用flag.Parse来更新标识变量的默认值.非标识参数也可以从flag.Args()返回的字符串slice来访问,如果flag.Parse遇到错误,它输出一条帮助消息,然后调用os.Exit(2)来结束程序.
[administrator@administrator-dev 20210312]$ ./04 -help Usage of ./04: -n omit trailing newline -s string separator (default " ")
6.flag.Duration
flag.Duration函数创建了一个time.Duration类型的标志变量
var period = flag.Duration("period", 1*time.Second, "sleep period") func main() { flag.Parse() fmt.Printf("Sleeping for %v...", *period) time.Sleep(*period) fmt.Println() }
7.命令行读取参数
flag.String("命令行参数的名称","默认值","提示")
package main import ( "flag" "fmt" ) func main() { //参数依次是命令行参数的名称,默认值,提示 surname := flag.String("surname", "王", "您的姓") //除了返回结果,还可以直接传入变量地址获取参数值 var personalName string flag.StringVar(&personalName, "personalName", "小二", "您的名") id := flag.Int("id", 0, "您的ID") //解析命令行参数 flag.Parse() fmt.Printf("I am %v %v, and my id is %v\n", *surname, personalName, *id) }
.