首页 文章资讯内容详情

Golang : cobra 包解析

2026-06-01 4 花语

本文内容纲要:

-Command结构体 -执行命令的逻辑 -总是执行根命令的ExecuteC()方法 -解析命令行子命令 -为根命令添加help子命令 -为命令添加helpflag -输出help信息 -总结

笔者在《Golang:cobra包简介》一文中简要的介绍了cobra包及其基本的用法,本文我们从代码的角度来了解下cobra的核心逻辑。

Command结构体

Command结构体是cobra抽象出来的核心概念,它的实例表示一个命令或者是一个命令的子命令。下面的代码仅展示Command结构体中一些比较重要的字段:

typeCommandstruct{ //用户通过指定Run函数来完成命令 //PreRun和PostRun则允许用户在Run运行的前后时机执行自定义代码 PersistentPreRunfunc(cmd*Command,args[]string) PreRunfunc(cmd*Command,args[]string) Runfunc(cmd*Command,args[]string) PostRunfunc(cmd*Command,args[]string) PersistentPostRunfunc(cmd*Command,args[]string) //commands字段包含了该命令的所有子命令 commands[]*Command //parent字段记录了该命令的父命令 parent*Command //该命令的help子命令 helpCommand*Command ... }

执行命令的逻辑

cobra包启动程序执行的代码一般为:

cmd.Execute()

Execute()函数会调用我们定义的rootCmd(Command的一个实例)的Execute()方法。

在Command的Execute()方法中又调用了Command的ExecuteC()方法,我们可以通过下面的调用堆栈看到执行命令逻辑的调用过程: cmd.Execute()->//main.go rootCmd.Execute()->//root.go c.ExecuteC()->//command.go cmd.execute(flags)->//command.go c.Run()//command.go

c.Run()方法即用户为命令(Command)设置的执行逻辑。

总是执行根命令的ExecuteC()方法

为了确保命令行上的子命令、位置参数和Flags能够被准确的解析,cobra总是执行根命令的ExecuteC()方法,其实现为在ExecuteC()方法中找到根命令,然后执行根命令的ExecuteC()方法,其逻辑如下:

//ExecuteCexecutesthecommand. func(c*Command)ExecuteC()(cmd*Command,errerror){ //Regardlessofwhatcommandexecuteiscalledon,runonRootonly ifc.HasParent(){ returnc.Root().ExecuteC() } ... }

解析命令行子命令

ExecuteC()方法中,在执行execute()方法前,需要先通过Find()方法解析命令行上的子命令:

cmd,flags,err=c.Find(args)

比如我们执行下面的命令:

$./myAppimage

解析出的cmd就是image子命令,接下来就是执行image子命令的执行逻辑。

Find()方法的逻辑如下:

$./myApphelpimage

这里的myApp在代码中就是rootCmd,Find()方法中定义了一个名称为innerfind的函数,innerfind从参数中解析出下一个名称,这里是help,然后从rootCmd开始查找解析出的名称help是不是当前命令的子命令,如果help是rootCmd的子命令,继续查找。接下来查找名称image,发现image不是help的子命令,innerfind函数就返回help命令。execute()方法中就执行这个找到的help子命令。

为根命令添加help子命令

在执行ExecuteC()方法时,cobra会为根命令添加一个help子命令,这个子命令主要用来提供子命令的帮助信息。因为任何一个程序都需要提供输出帮助信息的方式,所以cobra就为它实现了一套默认的逻辑。help子命令是通过InitDefaultHelpCmd()方法添加的,其实现代码如下:

//InitDefaultHelpCmdaddsdefaulthelpcommandtoc. //Itiscalledautomaticallybyexecutingthecorbycallinghelpandusage. //Ifcalreadyhashelpcommandorchasnosubcommands,itwilldonothing. func(c*Command)InitDefaultHelpCmd(){ if!c.HasSubCommands(){ return } ifc.helpCommand==nil{ c.helpCommand=&Command{ Use:"help[command]", Short:"Helpaboutanycommand", Long:`Helpprovideshelpforanycommandintheapplication. Simplytype`+c.Name()+`help[pathtocommand]forfulldetails.`, Run:func(c*Command,args[]string){ cmd,_,e:=c.Root().Find(args) ifcmd==nil||e!=nil{ c.Printf("Unknownhelptopic%#q\n",args) c.Root().Usage() }else{ cmd.InitDefaultHelpFlag()//makepossiblehelpflagtobeshown cmd.Help() } }, } } c.RemoveCommand(c.helpCommand) c.AddCommand(c.helpCommand) }

没有找到用户指定的子命令 如果没有找到用户指定的子命令,就输出错误信息,并调用根命令的Usage()方法:

c.Printf("Unknownhelptopic%#q\n",args) c.Root().Usage()

cobra默认提供的usage模板如下:

`Usage:{{if.Runnable}} {{.UseLine}}{{end}}{{if.HasAvailableSubCommands}} {{.CommandPath}}[command]{{end}}{{ifgt(len.Aliases)0}} Aliases: {{.NameAndAliases}}{{end}}{{if.HasExample}} Examples: {{.Example}}{{end}}{{if.HasAvailableSubCommands}} AvailableCommands:{{range.Commands}}{{if(or.IsAvailableCommand(eq.Name"help"))}} {{rpad.Name.NamePadding}}{{.Short}}{{end}}{{end}}{{end}}{{if.HasAvailableLocalFlags}} Flags: {{.LocalFlags.FlagUsages|trimTrailingWhitespaces}}{{end}}{{if.HasAvailableInheritedFlags}} GlobalFlags: {{.InheritedFlags.FlagUsages|trimTrailingWhitespaces}}{{end}}{{if.HasHelpSubCommands}} Additionalhelptopics:{{range.Commands}}{{if.IsAdditionalHelpTopicCommand}} {{rpad.CommandPath.CommandPathPadding}}{{.Short}}{{end}}{{end}}{{end}}{{if.HasAvailableSubCommands}} Use"{{.CommandPath}}[command]--help"formoreinformationaboutacommand.{{end}} `

找到了用户指定的子命令 如果找到用户指定的子命令,就为子命令添加默认的helpflag,并执行其Help()方法:

cmd.InitDefaultHelpFlag()//makepossiblehelpflagtobeshown cmd.Help()

为了解释help子命令的执行逻辑,我们举个例子。比如我们通过cobra实现了一个命令行程序myApp,它有一个子命令image,image也有一个子命令times。执行下面的命令:

$./myApphelpimage

在help命令的Run方法中,c为help命令,args为image。结果就是通过help查看image命令的帮助文档。如果image后面还有其他的子命令,比如:

$./myApphelpimagetimes

则c.Root().Find(args)逻辑会找出子命令times(此时args为imagetimes),最终由help查看times命令的帮助文档。

注意:help信息中包含usage信息。

为命令添加helpflag

除了在InitDefaultHelpCmd()方法中会调用InitDefaultHelpFlag()方法,在execute()方法中执行命令逻辑前也会调用InitDefaultHelpFlag()方法为命令添加默认的helpflag,

c.InitDefaultHelpFlag()

下面是InitDefaultHelpFlag()方法的实现:

//InitDefaultHelpFlagaddsdefaulthelpflagtoc. //Itiscalledautomaticallybyexecutingthecorbycallinghelpandusage. //Ifcalreadyhashelpflag,itwilldonothing. func(c*Command)InitDefaultHelpFlag(){ c.mergePersistentFlags() ifc.Flags().Lookup("help")==nil{ usage:="helpfor" ifc.Name()==""{ usage+="thiscommand" }else{ usage+=c.Name() } c.Flags().BoolP("help","h",false,usage) } }

这让我们不必为命令添加helpflag就可以直接使用!至于falg的解析,则是通过pflag包实现的,不了解pflag包的朋友可以参考《Golang:pflag包简介》。

输出help信息

不管是help命令还是helpfalg,最后都是通过HelpFunc()方法来获得输出help信息的逻辑:

//HelpFuncreturnseitherthefunctionsetbySetHelpFuncforthiscommand //oraparent,oritreturnsafunctionwithdefaulthelpbehavior. func(c*Command)HelpFunc()func(*Command,[]string){ ifc.helpFunc!=nil{ returnc.helpFunc } ifc.HasParent(){ returnc.Parent().HelpFunc() } returnfunc(c*Command,a[]string){ c.mergePersistentFlags() err:=tmpl(c.OutOrStdout(),c.HelpTemplate(),c) iferr!=nil{ c.Println(err) } } }

如果我们没有指定自定义的逻辑,就找父命令的,再没有就用cobra的默认逻辑。cobra默认设置的帮助模板如下(包含usage):

`{{with(or.Long.Short)}}{{.|trimTrailingWhitespaces}} {{end}}{{ifor.Runnable.HasSubCommands}}{{.UsageString}}{{end}}`

总结

本文简要介绍了cobra包的主要逻辑,虽然忽略了众多的实现细节,但梳理出了程序执行的主要过程,并对help子命令的实现以及helpflag的实现进行了介绍。希望对大家了解和使用cobra包有所帮助。

参考:

spf13/cobra

Golang之使用Cobra

MAKEYOUROWNCLIWITHGOLANGANDCOBRA

Cobra简介

golang命令行库cobra的使用

本文内容总结:Command结构体,执行命令的逻辑,总是执行根命令的ExecuteC()方法,解析命令行子命令,为根命令添加help子命令,为命令添加helpflag,输出help信息,总结,

原文链接:https://www.cnblogs.com/sparkdev/p/10868873.html