docker的cli是怎么解释参数的

接着上一篇docker命令attach源码分析,继续走

先看看prase的整体代码:

//比如docker attach XXXID -> XXXID = arguments
func (fs *FlagSet) Parse(arguments []string) error {
	fs.parsed = true 
	//打标志,作用:Parsed reports whether fs.Parse has been called,看看走到这一步没有
	fs.args = arguments 
	for {
		seen, name, err := fs.parseOne()
		//关键点就是parseOne函数的执行,seen返回true就继续
		if seen {
			continue
		}
		if err == nil {
			break
		}
		if err == ErrRetry {
			if len(name) > 1 {
				err = nil
				//strings.Split("123","")="1 2 3" ,把string转化成一个数组{1,2,3}
				for _, letter := range strings.Split(name, "") {
					fs.args = append([]string{"-" + letter}, fs.args...)
					//打上标记"-",继续parseOne
					seen2, _, err2 := fs.parseOne()
					if seen2 {
						continue
					}
					if err2 != nil {
					err = fs.failf("flag provided but not defined: -%s", name)
						break
					}
				}
				if err == nil {
					continue
				}
			} else {
				err = fs.failf("flag provided but not defined: -%s", name)
			}
		}
		switch fs.errorHandling {
		case ContinueOnError:
			return err
		case ExitOnError:
			os.Exit(2)
		case PanicOnError:
			panic(err)
		}
	}
	return nil
}

从Prase的函数来看,具体执行的关键步骤就是所谓的praseOne函数,一起来看看这个函数

func (fs *FlagSet) parseOne() (bool, string, error) {
	if len(fs.args) == 0 {
		return false, "", nil
	}
	//注意我们参数的类型arguments []string,string数组,s表示第一参数
	s := fs.args[0]
	
	//比如docker attach 命令后面接的参数就是ContainerID,s[0]!='-',返回咯
	//不要紧,回溯到parse,然后返回nil,所以这种参数不需要太多check和赋值flagset等额外操作
	
	if len(s) == 0 || s[0] != '-' || len(s) == 1 {
		return false, "", nil
	}
	
	
	if s[1] == '-' && len(s) == 2 { // "--" terminates the flags
		fs.args = fs.args[1:]
		return false, "", nil
	}
	
	//把第一个参数去掉'-'赋值给name
	name := s[1:]
	if len(name) == 0 || name[0] == '=' {
		return false, "", fs.failf("bad flag syntax: %s", s)
	}

	//指向下一个参数
	fs.args = fs.args[1:]
	hasValue := false
	value := ""
	//找到"="的位置,分离字符串
	if i := strings.Index(name, "="); i != -1 {
		value = trimQuotes(name[i+1:]) //"="后面就是value
		hasValue = true
		name = name[:i] // "=" 前面的就是name
	}
        //比如args = --log-driver=json-file  name = -log-driver value =json-file
	
	m := fs.formal
	flag, alreadythere := m[name] // map取值,判断是否存在
	//比如attach命令,fs.formal存在的name就是nostdin和sig-proxy,前文已提
	
	if !alreadythere {
		if name == "-help" || name == "help" || name == "h" { 
		// special case for nice help message.
			fs.usage()
			return false, "", ErrHelp
		}
		if len(name) > 0 && name[0] == '-' {
			return false, "", fs.failf("flag provided but not defined: -%s", name)
		}
		return false, name, ErrRetry
	}
	//Bool类型的设置值,hasValue是前面寻找"="时候判断的
	if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { 
	// special case: doesn't need an arg
		if hasValue {
		        //这里设置flag的值
			if err := fv.Set(value); err != nil {
	return false, "", fs.failf("invalid boolean value %q for  -%s: %v", value, name, err)
			}
		} else {
			fv.Set("true")
		}
	} else {
		// It must have a value, which might be the next argument.
		if !hasValue && len(fs.args) > 0 {
			// value is the next arg //没有找到"=" value就当下一个参数
			hasValue = true
			value, fs.args = fs.args[0], fs.args[1:]
		}
		if !hasValue {
			return false, "", fs.failf("flag needs an argument: -%s", name)
		}
		//设置非Bool的flag的值
		if err := flag.Value.Set(value); err != nil {
		return false, "", fs.failf("invalid value %q for flag -%s: %v", value, name, err)
		}
	}
	if fs.actual == nil {
		fs.actual = make(map[string]*Flag)
	}
	fs.actual[name] = flag //当前值赋值给fs.actual 
	//下面就是提示一些后期可能被修改的命令的名称,打杂的
	for i, n := range flag.Names {
		if n == fmt.Sprintf("#%s", name) {
			replacement := ""
			for j := i; j < len(flag.Names); j++ {
				if flag.Names[j][0] != '#' {
					replacement = flag.Names[j]
					break
				}
			}
			if replacement != "" {
                            fmt.Fprintf(fs.Out(), "Warning: '-%s' is deprecated, it will be replaced                                 by '-%s' soon. See usage.\n", name, replacement)
			} else {
				fmt.Fprintf(fs.Out(), "Warning: '-%s' is deprecated, it will be remo                                ved soon. See usage.\n", name)
			}
		}
	}
	return true, "", nil
}

还记得这中间漏掉了什么吗?当然就是flag的set是怎么实现的?

//Flag的Value本来就是一个接口interface,需要实现接口的String和Set方法,实现接口类型和变量类型的赋值
type Value interface {
	String() string
	Set(string) error
}
// -- bool Value
type boolValue bool

func newBoolValue(val bool, p *bool) *boolValue {
	*p = val
	return (*boolValue)(p)
}

//通过golang里面面向对象的接口实现,设置Set方法,不同类型Value的接口统一,实现可以扩展
func (b *boolValue) Set(s string) error {
	v, err := strconv.ParseBool(s)
	*b = boolValue(v)
	return err
}

func (b *boolValue) Get() interface{} { return bool(*b) }

func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) }

func (b *boolValue) IsBoolFlag() bool { return true }

为此,fs.actual[name]已经赋了Flag类型的值,命令行的参数值的设置告一段落,下一次继续分享docker cli和docker daemon的交互


你可能感兴趣的:(docker的cli是怎么解释参数的)