分享
  1. 首页
  2. 文章

golang标准库之flag

风铃草613 · · 1182 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

flag包实现了简单的命令行参数解析,支持bool、int、int64、uint、uint64、float64、string和time.Duration八种类型的命令行解析。

使用方法

注册flag流程如下:

import "flag"
var ip = flag.Int("flagname", 1234, "help message for flagname") // 返回指针类型,访问时需要加*
fmt.Println("ip has value ", *ip)
var flagvar int
func init() {
 flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") // 放在init函数中,确保初始化时完成flag的注册
fmt.Println("flagvar has value ", flagvar)
}

在所有flag都注册之后,调用:

flag.Parse()

需要注意的几点:

  • flag包对于其它类型支持-flag x-flag=x两种情况;
  • flag包对于隐式bool类型支持-flag,默认为True;
  • flag包对于显式bool类型支持-flag=false;
    显式bool类型的支持并非flag包实现,而是调用strconv包实现的
bool类型 支持的bool类型显示字符串
true "1", "t", "T", "true", "TRUE", "True"
false "0", "f", "F", "false", "FALSE", "False"

源码学习

接口设计
type Value interface {
 String() string
 Set(string) error
}
type Getter interface {
 Value
 Get() interface{}
}
// ErrorHandling定义了解析错误时FlagSet.Parse怎么处理
type ErrorHandling int
const (
 ContinueOnError ErrorHandling = iota // Return a descriptive error.
 ExitOnError // Call os.Exit(2).
 PanicOnError // Call panic with a descriptive error.
)
// 结构体Flag表示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表示一组flag,空值的FlagSet没有name,但是包含ContinueOnError。
// Flag的names在FlagSet内唯一,定义已经存在的name导致panic。
type FlagSet struct {
 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
 // 解析错误时调用Usage函数,也可以替换成错误处理函数。
 // Usage函数调用之后,会根据ErrorHandling的值作出相应处理。
 Usage func()
}
1. import "flag"

import 后会执行初始化FlagSet的操作

var CommandLine = NewFlagSet(os.Args[0], ExitOnError) // 实例化FlagSet为CommandLine,注册errorHandling为解析错误时退出程序
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
 f := &FlagSet{
 name: name,
 errorHandling: errorHandling,
 }
 f.Usage = f.defaultUsage
 return f
}
2. 注册flag

这部分主要工作是把nameflag以键值对存入字典formal,用于在Parse阶段查询。以int类型为例,注册flag的方法主要有4个,如下所示,本质上都是在调用func (f *FlagSet) Var(value Value, name string, usage string)方法。

func (f *FlagSet) IntVar(p *int, name string, value int, usage string) {
 f.Var(newIntValue(value, p), name, usage)
}
func IntVar(p *int, name string, value int, usage string) {
 CommandLine.Var(newIntValue(value, p), name, usage)
}
func (f *FlagSet) Int(name string, value int, usage string) *int {
 p := new(int)
 f.IntVar(p, name, value, usage)
 return p
}
func Int(name string, value int, usage string) *int {
 return CommandLine.Int(name, value, usage)
}
func (f *FlagSet) Var(value Value, name string, usage string) {
 // Remember the default value as a string; it won't change.
 flag := &Flag{name, usage, value, value.String()}
 _, alreadythere := f.formal[name] // 查询formal字典,是否存在新flag的name
 if alreadythere { // 已存在,触发panic
 var msg string
 if f.name == "" {
 msg = fmt.Sprintf("flag redefined: %s", name)
 } else {
 msg = fmt.Sprintf("%s flag redefined: %s", f.name, name)
 }
 fmt.Fprintln(f.Output(), msg)
 panic(msg) // Happens only if flags are declared with identical names
 }
 if f.formal == nil { // formal为空时,需要初始化,否则无法填入
 f.formal = make(map[string]*Flag)
 }
 f.formal[name] = flag // 执行成功,当前flag填入formal
}

其中入参Value接口定义如下:

type intValue int
func newIntValue(val int, p *int) *intValue {
 *p = val
 return (*intValue)(p)
}
func (i *intValue) Set(s string) error {
 v, err := strconv.ParseInt(s, 0, strconv.IntSize)
 if err != nil {
 err = numError(err)
 }
 *i = intValue(v)
 return err
}
func (i *intValue) Get() interface{} { return int(*i) }
func (i *intValue) String() string { return strconv.Itoa(int(*i)) }
3. Parse

Parse函数循环调用parseOne,每次解析一个flag,解析失败则触发errorHandling

func Parse() {
 CommandLine.Parse(os.Args[1:]) // os.Args[0]是程序名,os.Args[1:]为参数,开始解析参数
}
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 { // err非空时,根据errorHandling执行相应操作
 case ContinueOnError:
 return err
 case ExitOnError:
 os.Exit(2)
 case PanicOnError:
 panic(err)
 }
 }
 return nil
}

parseOne完成了解析工作的主要内容,函数如下所示:

// parseOne parses one flag. It reports whether a flag was seen.
func (f *FlagSet) parseOne() (bool, error) {
 if len(f.args) == 0 {
 return false, nil
 }
 s := f.args[0] // ---------------解析flag
 if len(s) < 2 || s[0] != '-' { // 必须以'-'开头才能识别
 return false, nil
 }
 numMinuses := 1
 if s[1] == '-' {
 numMinuses++
 if len(s) == 2 { // "--" 直接返回,停止解析
 f.args = f.args[1:]
 return false, nil
 }
 }
 name := s[numMinuses:] // -flag -> flag, --flag -> flag, 去除开头的一个或两个"-", 存入name
 if len(name) == 0 || name[0] == '-' || name[0] == '=' {
 return false, f.failf("bad flag syntax: %s", s)
 }
 // it's a flag. does it have an argument?
 f.args = f.args[1:] // ---------------解析value
 hasValue := false
 value := ""
 for i := 1; i < len(name); i++ { // equals cannot be first
 if name[i] == '=' { // 解析-flag=value, --flag=value
 value = name[i+1:]
 hasValue = true
 name = name[0:i]
 break
 }
 }
 m := f.formal
 flag, alreadythere := m[name] // BUG
 if !alreadythere {
 if name == "help" || name == "h" { // special case for nice help message.
 f.usage()
 return false, ErrHelp
 }
 return false, f.failf("flag provided but not defined: -%s", name)
 }
 if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // bool类型,支持-flag和-flag=false两种
 if hasValue {
 if err := fv.Set(value); err != nil { // 解析-flag=false形式
 return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err)
 }
 } else {
 if err := fv.Set("true"); err != nil { // 解析-flag,默认为True
 return false, f.failf("invalid boolean flag %s: %v", name, err)
 }
 }
 } else { // 其它类型,支持-flag=123、-flag 123两种
 if !hasValue && len(f.args) > 0 {
 hasValue = true
 value, f.args = f.args[0], f.args[1:] // f.args = f.args[1:],索引0为该flag对应的值,[1:]存入放f.args继续解析
 }
 if !hasValue {
 return false, f.failf("flag needs an argument: -%s", name)
 }
 if err := flag.Value.Set(value); err != nil { // value写入变量
 return false, f.failf("invalid value %q for flag -%s: %v", value, name, err)
 }
 }
 if f.actual == nil {
 f.actual = make(map[string]*Flag)
 }
 f.actual[name] = flag // 解析之后的有效flag存入f.actual
 return true, nil
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:风铃草613

查看原文:golang标准库之flag

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1182 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏