接着上一篇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的交互