• 小白学标准库之 flag



    Go 提供了解析命令行参数的 flag 包,本文旨在介绍 flag 的使用及内部实现等。

    1. flag 包使用及实现

    type PropertyOfPod struct {
    	Namespace *string
    	PodName   *string
    	Phase     *string
    }
    
    var pod = PropertyOfPod{}
    
    func init() {
        // String defines a string flag with specified name, default value, and usage string.
        // The return value is the address of a string variable that stores the value of the flag.
    
    	pod.Namespace = flag.String("namespace", "default", "resource field for pod")
    	pod.PodName = flag.String("name", "", "pod name")
    
    	pod.Phase = new(string)
    
        // StringVar defines a string flag with specified name, default value, and usage string.
        // The argument p points to a string variable in which to store the value of the flag.
    
    	flag.StringVar(pod.Phase, "phase", "running", "pod phase")
    }
    
    func main() {
    	flag.Parse()
    
    	fmt.Printf("pod property:
    namespace: %v, pod name: %v, phase: %v
    ", *pod.Namespace, *pod.PodName, *pod.Phase)
    }
    

    调用 flag 包的 String 函数定义 flag。示例中,通过 String 和 StringVar 两种方式定义 flag。

    具体定义 flag 的 String 函数做了什么呢?接着往下看 String 函数:

    func String(name string, value string, usage string) *string {
    	return CommandLine.String(name, value, usage)
    }
    

    String 通过实例化类 CommandLine 调用 String 方法。其中 CommandLine 的类结构体定义为:

    var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
    
    type FlagSet struct {
    	Usage func()
    
    	name          string                                // 每个 FlagSet 有唯一的 name
    	parsed        bool                                  // parsed 标志记录 FlagSet 是否解析命令行参数
    	actual        map[string]*Flag                      // 最终记录的实际可用标志
    	formal        map[string]*Flag                      // 标志信息,未“过滤”
    	args          []string // arguments after flags     // 命令行传入参数 os.Args[1:] 写入到 args 中
    	errorHandling ErrorHandling
    	output        io.Writer // nil means stderr; use Output() accessor
    }
    

    FlagSet 的 String 方法又做了什么呢?接着往下看:

    func (f *FlagSet) String(name string, value string, usage string) *string {
    	p := new(string)
    	f.StringVar(p, name, value, usage)
    	return p
    }
    
    func (f *FlagSet) StringVar(p *string, name string, value string, usage string) {
    	f.Var(newStringValue(value, p), name, usage)
    }
    

    在 String 方法内创建了临时指针变量 p, p 的值被传入到 StringVar 函数。这和示例直接调用 StringVar 是一样的,在 newStringValue 函数中,p 将指向 value 的地址:

    // -- string Value
    type stringValue string
    
    func newStringValue(val string, p *string) *stringValue {
    	*p = val
    	return (*stringValue)(p)
    }
    

    最后调用 Var 方法实现 flag 的定义,Var 方法的第一个参数值得一说,它接受的是接口类型 Value 的值,为什么接受接口类型是因为这里需要多态实现不仅定义 String flag,也能定义 Int,Bool 等类型的 flag。

    Var 方法将外部传入参数定义到 Flag 结构体中,并作为值赋给 formal:

    func (f *FlagSet) Var(value Value, name string, usage string) {
    	flag := &Flag{name, usage, value, value.String()}
    	_, alreadythere := f.formal[name]
    	if alreadythere {
    		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 {
    		f.formal = make(map[string]*Flag)
    	}
    	f.formal[name] = flag
    }
    

    定义了 flag 之后,还需要 parse 对传入的 flag 进行解析,实例中调用 flag 的 Parse 函数实现解析:

    func Parse() {
    	// Ignore errors; CommandLine is set for ExitOnError.
    	CommandLine.Parse(os.Args[1:])
    }
    
    func (f *FlagSet) Parse(arguments []string) error {
    	f.parsed = true                 // parse 时将 parsed 标志记为 true
    	f.args = arguments
    	for {
    		seen, err := f.parseOne()
    		if seen {
    			continue
    		}
    		if err == nil {
    			break
    		}
    		...
    	}
    	return nil
    }
    

    其中,parseOne 方法解析 args 参数中的 flag,并将解析的 flag 赋给 map actual。

    2. flag 方法

    2.1 Visit

    // Visit visits the command-line flags in lexicographical order, calling fn
    // for each. It visits only those flags that have been set.
    
    flag.Visit(func(f *flag.Flag) {
    		key := strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_"))
    		Flag_visit[key] = string(f.Value.String())
    	})
    	fmt.Printf("Flag_visit: %v
    ", Flag_visit)
    

    2.2 VisitAll

    // VisitAll visits the command-line flags in lexicographical order, calling
    // fn for each. It visits all flags, even those not set.
    
    flag.VisitAll(func(f *flag.Flag) {
    		key := strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_"))
    		Flag_visit[key] = string(f.Value.String())
    	})
    	fmt.Printf("Flag_visit: %v
    ", Flag_visit)
    
    芝兰生于空谷,不以无人而不芳。
  • 相关阅读:
    C# CodeFirst(EF框架)代码优先创建数据库
    Entity Framework 配置关系(1对1,1对0)
    Entity Framework 配置关系(1对1,1对0)
    EFDbContext的使用
    EFDbContext的使用
    编程模式·观察者模式、事件通知、消息队列三者区别
    编程模式·观察者模式、事件通知、消息队列三者区别
    设计模式发布订阅方式实现异步并发
    设计模式发布订阅方式实现异步并发
    关于访问asp.net网站时登录后的奇怪问题
  • 原文地址:https://www.cnblogs.com/xingzheanan/p/15431407.html
Copyright © 2020-2023  润新知