命令记不住

登陆

标准库 flag

标准库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使程序忽略正常输出时结尾的换行符,-s sep使用替换默认参数输出时使用的空格分割符

[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)

}

.

推荐阅读