flag包概述
flag包实现了命令行参数的解析
flag包的工作流程
基本分为三步:
- 注册flag,主要用flag.String(), Bool(), Int()等函数注册flag,或者用flag.BoolVar(),StringVar(),IntVar()等函数把flag绑定到变量.
- 解析flag,使用flag.Parse()函数解析在命令行使用了的flag。
- 最后根据命令行输入的flag处理逻辑
通过一个栗子感受一下
package main
import (
"flag"
"fmt"
"os"
)
var (
// 1. 使用flag.String(), Bool(), Int()等函数注册flag,解析后保存到bool,int,string类型的指针
n = flag.Int("n", 1, "print times .")
s = flag.String("s", "hello world", "print strings.")
// 2. 也可以先声明变量,再用flag.BoolVar把flag绑定到变量
v bool
)
func init() {
// 3. 绑定flag到变量
flag.BoolVar(&v, "v", false, "print version and exit.")
// 4. flag.usage是一个函数变量,在解析flag发生错误时被调用,输出一个说明文本到命令行,可以重写usage的内容
flag.Usage = usage
}
func main() {
// 5. 解析命令行flag,这是使用命令行参数之前首先要做的事
flag.Parse()
// 6. bool类型flag可以接收的值:1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False
// bool类型也可以只写flag名,省略赋值部分,等价与赋值为true."例如:cmd -h "
if v {
fmt.Fprintln(os.Stderr, "flag demo version: 1.0.0")
os.Exit(0)
}
// 7. flag.Lookup从命令行flag中返回指定名称的flag,如果没有则返回nil
if flag.Lookup("s")!=nil {
if *s != "" {
for i := 0; i < *n; i++ {
// 8. 命令行flag语法,可以使用1个或2个'-'号,效果是一样的。
// -flag
// -flag=x
// -flag x // 只有非bool类型的flag可以
fmt.Fprintln(os.Stdout, *s)
}
}
}
// 9. flag.NFlag返回在命令行使用的flag数
if flag.NFlag() == 0 {
flag.Usage()
}
// 10. visit,遍历所有在命令行中使用的flag,执行自定义函数func
flag.Visit(func(f *flag.Flag) {
fmt.Fprintln(os.Stdout,f.Name,f.Value)
})
// 11. visitAll,遍历所有注册过的flag,执行自定义函数func
flag.VisitAll(func(f *flag.Flag) {
fmt.Fprintln(os.Stdout,f.Name,f.Value)
})
// 12. 当flag -help或 -h 被调用但没有注册这个flag时,就会返回flag.usage。
// ErrHelp is the error returned if the -help or -h flag is invoked
}
func usage() {
fmt.Fprint(os.Stderr, `
Usage:flagDemo [options]
Options:
`)
flag.PrintDefaults()
}
复制代码
主要结构体
Flag
注册flag就是初始化这个Flag
// A Flag represents the state of a flag.
type Flag struct {
Name string // name as it appears on command line
Usage string // help message
Value Value // value as set
DefValue string // default value (as text); for usage message
}
复制代码
FlagSet
// A FlagSet represents a set of defined flags. The zero value of a FlagSet
// has no name and has ContinueOnError error handling.
//
// Flag names must be unique within a FlagSet. An attempt to define a flag whose
// name is already in use will cause a panic.
type FlagSet struct {
// Usage is the function called when an error occurs while parsing flags.
// The field is a function (not a method) that may be changed to point to
// a custom error handler. What happens after Usage is called depends
// on the ErrorHandling setting; for the command line, this defaults
// to ExitOnError, which exits the program after calling Usage.
Usage func()
name string
parsed bool
actual map[string]*Flag
formal map[string]*Flag
args []string // arguments after flags
errorHandling ErrorHandling
output io.Writer // nil means stderr; use out() accessor
}
复制代码
在命令行中使用的所有flag会被解析成一个FlagSet, 我们可以直接使用flag包的Bool(),BoolVar,Parse(),Nflag(),Usage,Visit()等函数去处理flag,这是因为flag包中已经定义了一个FlagSet,名为CommandLine。 我们也可以用NewFlagSet()自己定义一个新的FlagSet去处理命令行flag。
区别在于,CommandLine会在Parse()发生错误时退出,而NewFlagSet()可以自定义ErrorHandling
// CommandLine is the default set of command-line flags, parsed from os.Args.
// The top-level functions such as BoolVar, Arg, and so on are wrappers for the
// methods of CommandLine.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
复制代码
// NewFlagSet returns a new, empty flag set with the specified name and
// error handling property. If the name is not empty, it will be printed
// in the default usage message and in error messages.
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
f := &FlagSet{
name: name,
errorHandling: errorHandling,
}
f.Usage = f.defaultUsage
return f
}
复制代码
func (f *FlagSet) Parse(arguments []string) error {
f.parsed = true
f.args = arguments
for {
seen, err := f.parseOne()
if seen {
continue
}
if err == nil {
break
}
switch f.errorHandling {
case ContinueOnError:
return err
case ExitOnError:
os.Exit(2)
case PanicOnError:
panic(err)
}
}
return nil
}
复制代码
方法介绍
Bool(),String(),Int(),BoolVar,StringVar(),IntVar(),Var()都是注册flag的方法
Parse()是从命令行解析flag的方法
Lookup(name string),判断是否使用了这个flag
NFlag(),返回命令行使用的flag数
Visit(),按照字母顺序遍历所有在命令行使用的flag
VisitAll(),按照字母顺序遍历所有注册过的flag
PrintDefaults(),打印所有已定义参数的默认值,默认输出到标准错误。(用 VisitAll 实现)
自定义 Value
只要实现 flag.Value 接口即可(要求 receiver 是指针),这时候可以这样注册flag:
flag.Var(&flagVal, "name", "help message for flagname")
复制代码
// 1. 定义一个类型,用于增加该类型方法
type sliceValue []string
// 2. 实现flag包中的Value接口,将命令行接收到的值用,分隔存到slice里
func (s *sliceValue) Set(val string) error {
*s = strings.Split(val, ",")
return nil
}
func (s *sliceValue) String() string {
return strings.Join(*s, ",")
}
var sv *sliceValue
func init() {
// 3. 分配内存
sv = new(sliceValue)
// 4. 自定义类型绑定到flag
flag.Var(sv, "sv", "custom sliceValue")
}
复制代码
总结
标准库中的flag包足够满足一些简单的需求,下面这两个库实现了更多命令行功能: