一个命令如attach命令,定义为一个Flag,首先看一下FlagSet的结构体表示: type FlagSet struct { // 比如我们在解释一个Flag如attach命令(docker attach xxx) // 如果出错咯,我们就输出错误提示到Usage和ShortUsage那个指向的匿名函数 Usage func() ShortUsage func() name string // "attach"..... parsed bool // 是够被解析过的标志 actual map[string]*Flag // 当前解析的Flag formal map[string]*Flag // 以前解析的Flag args []string // 解析参数,如 docker attach ID ID即为参数 errorHandling ErrorHandling // 错误的钩子 output io.Writer // 错误输出IO,stderr/stdout nArgRequirements []nArgRequirement // 需要参数的个数,比如docker attach xx 则需要参数为1 }
接着,将分析一个具体的命令的执行过程,比如docker attach的执行过程源码分析
函数定义:func (cli *DockerCli) CmdAttach(args ...string) error docker client API命令统一为:Cmd+"命令name" 设置flagset,思想类似map[key]value,先赋值key和默认的value,设置一些参数,方便后面Prase参数的时候判断key是否存在,以及初始化的默认值,参数个数等等.... cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, "Attach to a running container", true) //看一看Subcmd的函数: func Subcmd(name string, synopses []string, description string, exitOnError bool) *flag.FlagSet { var errorHandling flag.ErrorHandling if exitOnError { errorHandling = flag.ExitOnError } else { errorHandling = flag.ContinueOnError } flags := flag.NewFlagSet(name, errorHandling) //关键就是设置了这几个匿名函数,方便以后命令输入错误的时候提示 flags.Usage = func() { flags.ShortUsage() flags.PrintDefaults() } flags.ShortUsage = func() { options := "" if flags.FlagCountUndeprecated() > 0 { options = " [OPTIONS]" } if len(synopses) == 0 { synopses = []string{""} } // Allow for multiple command usage synopses. for i, synopsis := range synopses { lead := "\t" if i == 0 { // First line needs the word 'Usage'. lead = "Usage:\t" } if synopsis != "" { synopsis = " " + synopsis } fmt.Fprintf(flags.Out(), "\n%sdocker %s%s%s", lead, name, options, synopsis) } fmt.Fprintf(flags.Out(), "\n\n%s\n", description) } return flags } //一个NewFlagSet,就是生成一个FlagSet func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet { f := &FlagSet{ name: name, errorHandling: errorHandling, } return f }
再看看cmd.Bool是干了什么鬼?
noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN") proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process") //这两个鬼是返回的val所保存的地址 // []string{"#nostdin", "-no-stdin"} ->names ,false -> value,"Do not attach STDIN" ->usage func (fs *FlagSet) Bool(names []string, value bool, usage string) *bool { p := new(bool) fs.BoolVar(p, names, value, usage) return p //但是付汇的是这个p的地址 } //fs.BoolVar(p, names, value, usage)执行 func (fs *FlagSet) BoolVar(p *bool, names []string, value bool, usage string) { fs.Var(newBoolValue(value, p), names, usage) } // newBoolValue的功能是某个地址p赋值val,然后返回地址, //注意go语言中,return 栈上的变量会生成在堆上,不是临时的,这一点和c语言不一样 type boolValue bool func newBoolValue(val bool, p *bool) *boolValue { *p = val return (*boolValue)(p) } //Var 就是给FlagSet赋值上Flag func (fs *FlagSet) Var(value Value, names []string, usage string) { // Remember the default value as a string; it won't change. flag := &Flag{names, usage, value, value.String()} for _, name := range names { //循环的遍历每个命令的name,过滤掉前面的#字符 name = strings.TrimPrefix(name, "#") _, alreadythere := fs.formal[name] if alreadythere { var msg string if fs.name == "" { msg = fmt.Sprintf("flag redefined: %s", name) } else { msg = fmt.Sprintf("%s flag redefined: %s", fs.name, name) } fmt.Fprintln(fs.Out(), msg) panic(msg) // Happens only if flags are declared with identical names } //如果为空,则初始化map,map一定要先make初始化后才能使用 if fs.formal == nil { fs.formal = make(map[string]*Flag) } fs.formal[name] = flag } } //flag的定义简单: type Flag struct { Names []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 }
接下来再看看cmd.Require
cmd.Require(flag.Exact, 1) //flag.Exact = 0 后面定义可以看出来 func (fs *FlagSet) Require(nArgRequirementType nArgRequirementType, nArg int) { fs.nArgRequirements = append(fs.nArgRequirements, nArgRequirement{nArgRequirementType, nArg}) } //切片的append type nArgRequirementType int const ( Exact nArgRequirementType = iota Max Min ) //说明docker attach需要一个额外的参数
再来看看cmd.ParseFlags
cmd.ParseFlags(args, true) func (fs *FlagSet) ParseFlags(args []string, withHelp bool) error { var help *bool if withHelp { help = fs.Bool([]string{"#help", "-help"}, false, "Print usage") } if err := fs.Parse(args); err != nil { return err } if help != nil && *help { fs.SetOutput(os.Stdout) fs.Usage() os.Exit(0) } if str := fs.CheckArgs(); str != "" { fs.SetOutput(os.Stderr) fs.ReportError(str, withHelp) fs.ShortUsage() os.Exit(1) } return nil } //主要是检测参数是否正确,如果不正确,则输出Usage/ShortUsage或者withHelp等等提示语 func (fs *FlagSet) CheckArgs() (message string) { for _, req := range fs.nArgRequirements { var arguments string //检测参数的提示语 if req.N == 1 { arguments = "1 argument" } else { arguments = fmt.Sprintf("%d arguments", req.N) } str := func(kind string) string { return fmt.Sprintf("%q requires %s%s", fs.name, kind, arguments) } switch req.Type { case Exact://attach命令现在默认是Exact if fs.NArg() != req.N { return str("") } case Max: if fs.NArg() > req.N { return str("a maximum of ") } case Min: if fs.NArg() < req.N { return str("a minimum of ") } } } return "" }
这次就讲到这里咯,下一次再继续分析一下参数prase的具体过程和cli.call,docker client和docker daemon交互的过程