golang应用构建三剑客--pflag_viper_cobra
一、基础包
1. os基础处理
os包中有一个string类型的切片变量os.Args,其用来处理一些基本的命令行参数,它在程序启动后读取命令行输入的参数。参数会放置在切片os.Args[]中(以空格分隔),从索引1开始(os.Args[0]放的是程序本身的名字)。
fmt.Println("Parameters:", os.Args[1:])
2. flag参数解析
flag包可以用来解析命令行选项,但通常被用来替换基本常量。例如,在某些情况下希望在命令行给常量一些不一样的值。
type Flag struct { Name 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 }
flag的使用规则是:首先定义flag(定义的flag会被解析),然后使用Parse()解析flag,解析后已定义的flag可以直接使用,未定义的剩余的flag可通过Arg(i)单独获取或通过Args()切片整个获取。
定义flag
func String(name string, value string, usage string) *string func StringVar(p *string, name string, value string, usage string) func Int(name string, value int, usage string) *int func IntVar(p *int, name string, value int, usage string)
解析flag
func Parse()
Parse() parses the command-line flags from os.Args[1:]. Must be called after all flags are defined and before flags are accessed by the program.
func Arg(i int) string func Args() []string
Arg returns the i'th command-line argument. Arg(0) is the first remaining argument after flags have been processed.
Args returns the non-flag command-line arguments.
After parsing, the arguments following the flags are available as the slice flag.Args() or individually as flag.Arg(i). The arguments are indexed from 0 through flag.NArg()-1.
func NArg() int
NArg is the number of arguments remaining after flags have been processed.
Flags may then be used directly. If you're using the flags themselves, they are all pointers; if you bind to variables, they're values.
package main import ( "fmt" "flag" ) func main(){ var new_line = flag.Bool("n", false, "new line") var max_num int flag.IntVar(&max_num, "MAX_NUM", 100, "the num max") flag.PrintDefaults() flag.Parse() fmt.Println("There are", flag.NFlag(), "remaining args, they are:", flag.Args()) fmt.Println("n has value: ", *new_line) fmt.Println("MAX_NJUM has value: ", max_num) } $ go build -o flag flag.go $ ./flag -MAX_NUM int the num max (default 100) -n new line There are 0 remaining args, they are: [] n has value: false MAX_NJUM has value: 100 $ ./flag -n -MAX_NUM=1000 wang qin -MAX_NUM int the num max (default 100) -n new line There are 2 remaining args, they are: [wang qin] n has value: true MAX_NJUM has value: 1000
二、pflag--命令参数解析
1. 数据结构
Pflag 可以对命令行参数进行处理,一个命令行参数在 Pflag 包中会解析为一个 Flag 类型的变量。
type Flag struct {
Name string // flag长选项的名称
Shorthand string // flag短选项的名称,一个缩写的字符
Usage string // flag的使用文本
Value Value // flag的值
DefValue string // flag的默认值
Changed bool // 记录flag的值是否有被设置过
NoOptDefVal string // 当flag出现在命令行,但是没有指定选项值时的默认值
Deprecated string // 记录该flag是否被放弃
Hidden bool // 如果值为true,则从help/usage输出信息中隐藏该flag
ShorthandDeprecated string // 如果flag的短选项被废弃,当使用flag的短选项时打印该信息
Annotations map[string][]string // 给flag设置注解
}
Flag 的值是一个 Value 类型的接口:
type Value interface {
String() string // 将flag类型的值转换为string类型的值,并返回string的内容
Set(string) error // 将string类型的值转换为flag类型的值,转换失败报错
Type() string // 返回flag的类型,例如:string、int、ip等
}
通过将 Flag 的值抽象成一个 interface 接口,我们就可以自定义 Flag 的类型了。只要实现了 Value 接口的结构体,就是一个新类型。
Pflag 除了支持单个的 Flag 之外,还支持 FlagSet。FlagSet 是一些预先定义好的 Flag 的集合,几乎所有的 Pflag 操作,都需要借助 FlagSet 提供的方法来完成。在实际开发中,我们可以使用两种方法来获取并使用 FlagSet:
- 方法一,调用 NewFlagSet 创建一个 FlagSet。
- 方法二,使用 Pflag 包定义的全局 FlagSet:CommandLine。实际上 CommandLine 也是由 NewFlagSet 函数创建的。
2. pflag使用
1) 命令行参数定义
var name = pflag.String("name", "colin", "Input Your Name") // 长选项、默认值和usage,并将标志值存储在指针中
var name = pflag.StringP("name", "n", "colin", "Input Your Name") // 长选项、短选项、默认值、usage,返回指针
var name string
pflag.StringVar(&name, "name", "colin", "Input Your Name")
var name string
pflag.StringVarP(&name, "name", "n","colin", "Input Your Name")
- 函数名带Var说明是将标志的值绑定到变量,否则是将标志的值存储在指针中。
- 函数名带P说明支持短选项,否则不支持短选项。
2)使用Get<type>获取参数的值。
3)获取非选项参数。
在定义完标志之后,可以调用pflag.Parse()来解析定义的标志。解析后,可通过pflag.Args()返回所有的非选项参数,pflag.NArg()返回非参数选项个数,通过pflag.Arg(i)返回第 i 个非选项参数。参数下标 0 到 pflag.NArg() - 1。
4)指定了选项但是没有指定选项值时的默认值。
创建一个 Flag 后,可以为这个 Flag 设置pflag.NoOptDefVal。如果一个 Flag 具有 NoOptDefVal,并且该 Flag 在命令行上没有设置这个 Flag 的值,则该标志将设置为 NoOptDefVal 指定的值。例如:
var ip = pflag.IntP("flagname", "f", 1234, "help message")
pflag.Lookup("flagname").NoOptDefVal = "4321"
--flagname=6666 ip=6666
--flagname ip=4321
[nothing] ip=1234
5)弃用标志或者标志的简写。
// deprecate a flag by specifying its name and a usage message
pflag.CommandLine.MarkDeprecated("logmode", "please use --log-mode instead")
6)保留名为 port 的标志,但是弃用它的简写形式。
pflag.IntVarP(&port, "port", "P", 3306, "MySQL service host port.")
// deprecate a flag shorthand by specifying its flag name and a usage message
pflag.CommandLine.MarkShorthandDeprecated("port", "please use --port only")
7)隐藏标记
可以将 Flag 标记为隐藏的,这意味着它仍将正常运行,但不会显示在 usage/help 文本中。例如:隐藏名为 secretFlag 的标志,只在内部使用,并且不希望它显示在帮助文本或者使用文本中。
// hide a flag by specifying its name
pflag.CommandLine.MarkHidden("secretFlag")
三、viper--配置文件解析
Viper 是 Go 应用程序现代化的、完整的解决方案,能够处理不同格式的配置文件,让我们在构建现代应用程序时,不必担心配置文件格式。
Viper 可以从不同的位置读取配置,不同位置的配置具有不同的优先级,高优先级的配置会覆盖低优先级相同的配置,按优先级从高到低排列如下:
- 通过 viper.Set 函数显示设置的配置
- 命令行参数
- 环境变量
- 配置文件
- Key/Value 存储
- 默认值
这里需要注意,Viper 配置键不区分大小写。
package main
import (
"fmt"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
var (
cfg = pflag.StringP("config", "c", "", "Configuration file.")
help = pflag.BoolP("help", "h", false, "Show this help message.")
)
func main() {
pflag.Parse()
if *help {
pflag.Usage()
return
}
// 从配置文件中读取配置
if *cfg != "" {
viper.SetConfigFile(*cfg) // 指定配置文件名
viper.SetConfigType("yaml") // 如果配置文件名中没有文件扩展名,则需要指定配置文件的格式,告诉viper以何种格式解析文件
} else {
viper.AddConfigPath(".") // 把当前目录加入到配置文件的搜索路径中
viper.AddConfigPath("$HOME/.iam") // 配置文件搜索路径,可以设置多个配置文件搜索路径
viper.SetConfigName("config") // 配置文件名称(没有文件扩展名)
}
if err := viper.ReadInConfig(); err != nil { // 读取配置文件。如果指定了配置文件名,则使用指定的配置文件,否则在注册的搜索路径中搜索
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
fmt.Printf("Used configuration file is: %s\n", viper.ConfigFileUsed())
}
1. 读入配置
读入配置,就是将配置读入到 Viper 中,有如下读入方式:
- 设置默认的配置文件名。
- 读取配置文件。
- 监听和重新读取配置文件。
- 从 io.Reader 读取配置。
- 从环境变量读取。
- 从命令行标志读取。
- 从远程 Key/Value 存储读取。
2. 读取配置
Get(key string) interface{}
Get<Type>(key string) <Type>
AllSettings() map[string]interface{}
IsSet(key string) : bool
每一个 Get 方法在找不到值的时候都会返回零值。可以是 Viper 支持的类型,首字母大写:Bool、Float64、Int、IntSlice、String、StringMap、StringMapString、StringSlice、Time、Duration。
3. set
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
viper.Set("user.username", "colin")
4. 使用环境变量
AutomaticEnv()
BindEnv(input …string) error
SetEnvPrefix(in string)
SetEnvKeyReplacer(r *strings.Replacer)
AllowEmptyEnv(allowEmptyEnv bool)
Viper 读取环境变量是区分大小写的。Viper 提供了一种机制来确保 Env 变量是唯一的。通过使用 SetEnvPrefix,可以告诉 Viper 在读取环境变量时使用前缀。BindEnv 和 AutomaticEnv 都将使用此前缀。
四、cobra--应用命令框架
Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files.
github:https://github.com/spf13/cobra.git
cobra基于command、arguments和flags组织命令行,如:
hugo server --port=1313
hugo是command,server是arguments,port是flags。
目录
一般程序目录结构如下:
app
├── cmd
│ ├── root.go
│ └── version.go
├── go.mod
└── main.go
main.go是主程序,cmd/root.go为基本命令文件,一般rootCmd表示在没有任何子命令的情况下的基本命令。
command
// Command is just that, a command for your application. // E.g. 'go run ...' - 'run' is the command. Cobra requires // you to define the usage and description as part of your command // definition to ensure usability. type Command struct { // Use is the one-line usage message. Use string // Aliases is an array of aliases that can be used instead of the first word in Use. Aliases []string // SuggestFor is an array of command names for which this command will be suggested - // similar to aliases but only suggests. SuggestFor []string // Short is the short description shown in the 'help' output. Short string // Long is the long message shown in the 'help <this-command>' output. Long string // Example is examples of how to use the command. Example string // ValidArgs is list of all valid non-flag arguments that are accepted in bash completions ValidArgs []string // Expected arguments Args PositionalArgs // ArgAliases is List of aliases for ValidArgs. // These are not suggested to the user in the bash completion, // but accepted if entered manually. ArgAliases []string // BashCompletionFunction is custom functions used by the bash autocompletion generator. BashCompletionFunction string // Deprecated defines, if this command is deprecated and should print this string when used. Deprecated string // Hidden defines, if this command is hidden and should NOT show up in the list of available commands. Hidden bool // Annotations are key/value pairs that can be used by applications to identify or // group commands. Annotations map[string]string // Version defines the version for this command. If this value is non-empty and the command does not // define a "version" flag, a "version" boolean flag will be added to the command and, if specified, // will print content of the "Version" variable. A shorthand "v" flag will also be added if the // command does not define one. Version string // The *Run functions are executed in the following order: // * PersistentPreRun() // * PreRun() // * Run() // * PostRun() // * PersistentPostRun() // All functions get the same args, the arguments after the command name. // // PersistentPreRun: children of this command will inherit and execute. PersistentPreRun func(cmd *Command, args []string) // PersistentPreRunE: PersistentPreRun but returns an error. PersistentPreRunE func(cmd *Command, args []string) error // PreRun: children of this command will not inherit. PreRun func(cmd *Command, args []string) // PreRunE: PreRun but returns an error. PreRunE func(cmd *Command, args []string) error // Run: Typically the actual work function. Most commands will only implement this. Run func(cmd *Command, args []string) // RunE: Run but returns an error. RunE func(cmd *Command, args []string) error // PostRun: run after the Run command. PostRun func(cmd *Command, args []string) // PostRunE: PostRun but returns an error. PostRunE func(cmd *Command, args []string) error // PersistentPostRun: children of this command will inherit and execute after PostRun. PersistentPostRun func(cmd *Command, args []string) // PersistentPostRunE: PersistentPostRun but returns an error. PersistentPostRunE func(cmd *Command, args []string) error // SilenceErrors is an option to quiet errors down stream. SilenceErrors bool // SilenceUsage is an option to silence usage when an error occurs. SilenceUsage bool // DisableFlagParsing disables the flag parsing. // If this is true all flags will be passed to the command as arguments. DisableFlagParsing bool // DisableAutoGenTag defines, if gen tag ("Auto generated by spf13/cobra...") // will be printed by generating docs for this command. DisableAutoGenTag bool // DisableFlagsInUseLine will disable the addition of [flags] to the usage // line of a command when printing help or generating docs DisableFlagsInUseLine bool // DisableSuggestions disables the suggestions based on Levenshtein distance // that go along with 'unknown command' messages. DisableSuggestions bool // SuggestionsMinimumDistance defines minimum levenshtein distance to display suggestions. // Must be > 0. SuggestionsMinimumDistance int // TraverseChildren parses flags on all parents before executing child command. TraverseChildren bool // FParseErrWhitelist flag parse errors to be ignored FParseErrWhitelist FParseErrWhitelist ctx context.Context // commands is the list of commands supported by this program. commands []*Command // parent is a parent command for this command. parent *Command // Max lengths of commands' string lengths for use in padding. commandsMaxUseLen int commandsMaxCommandPathLen int commandsMaxNameLen int // commandsAreSorted defines, if command slice are sorted or not. commandsAreSorted bool // commandCalledAs is the name or alias value used to call this command. commandCalledAs struct { name string called bool } // args is actual args parsed from flags. args []string // flagErrorBuf contains all error messages from pflag. flagErrorBuf *bytes.Buffer // flags is full set of flags. flags *flag.FlagSet // pflags contains persistent flags. pflags *flag.FlagSet // lflags contains local flags. lflags *flag.FlagSet // iflags contains inherited flags. iflags *flag.FlagSet // parentsPflags is all persistent flags of cmd's parents. parentsPflags *flag.FlagSet // globNormFunc is the global normalization function // that we can use on every pflag set and children commands globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName // usageFunc is usage func defined by user. usageFunc func(*Command) error // usageTemplate is usage template defined by user. usageTemplate string // flagErrorFunc is func defined by user and it's called when the parsing of // flags returns an error. flagErrorFunc func(*Command, error) error // helpTemplate is help template defined by user. helpTemplate string // helpFunc is help func defined by user. helpFunc func(*Command, []string) // helpCommand is command with usage 'help'. If it's not defined by user, // cobra uses default help command. helpCommand *Command // versionTemplate is the version template defined by user. versionTemplate string // inReader is a reader defined by the user that replaces stdin inReader io.Reader // outWriter is a writer defined by the user that replaces stdout outWriter io.Writer // errWriter is a writer defined by the user that replaces stderr errWriter io.Writer }
常用command示例如下:
var cmdPrint = &cobra.Command{ Use: "print [string to print]", Short: "Print anything to the screen", Long: `print is for printing anything back to the screen. For many years people have printed back to the screen.`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { fmt.Println("Print: " + strings.Join(args, " ")) }, }
flags
一般来说,在init()
函数中定义flags
和处理配置。
也可在cobra 命令后定义flags,并设置cobra.Oninitialize(initConfig),在initConfig()中处理flags。OnInitialize sets the passed functions to be run when each command's Execute method is called.
flags分为持久flags(Persistent Flags)和本地flags(local Flags)。
Local flags:flags仅适用于指定command。以serverCmd.Flags().StringVarP(&server.ServerPort, "port", "p", "50052", "server port")
为例,定义了一个flag
,值存储在&server.ServerPort
中,长命令为--port
,短命令为-p
,,默认值为50052
,命令的描述为server port
。
Persisten flags:flags适用于指定命令及其子命令。如rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output"),
flag
是可以持久的,这意味着该flag
将被分配给它所分配的命令以及该命令下的每个命令。对于全局标记,将标记作为根上的持久标志。
// https://github.com/spf13/pflag.git // StringVarP is like StringVar, but accepts a shorthand letter that can be used after a single dash. func (f *FlagSet) StringVarP(p *string, name, shorthand string, value string, usage string) { f.VarP(newStringValue(value, p), name, shorthand, usage) } // StringP is like String, but accepts a shorthand letter that can be used after a single dash. func (f *FlagSet) StringP(name, shorthand string, value string, usage string) *string { p := new(string) f.StringVarP(p, name, shorthand, value, usage) return p } // 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. func (f *FlagSet) String(name string, value string, usage string) *string { p := new(string) f.StringVarP(p, name, "", value, usage) return p }
当设定标记后,可以将标记绑定到Viper,这样就可以使用viper.Get()获取标志的值。可以将标记设置为必选。
var author string func init() { rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution") viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) rootCmd.MarkFlagRequired("author") }
Args
在命令的过程中,经常会传入非选项参数,并且需要对非选项参数进行验证,Cobra提供了机制来对非选项参数进行验证。可以使用Command的Args字段来验证非选项参数。
NoArgs
- the command will report an error if there are any positional args.ArbitraryArgs
- the command will accept any args.OnlyValidArgs
- the command will report an error if there are any positional args that are not in theValidArgs
field ofCommand
.MinimumNArgs(int)
- the command will report an error if there are not at least N positional args.MaximumNArgs(int)
- the command will report an error if there are more than N positional args.ExactArgs(int)
- the command will report an error if there are not exactly N positional args.ExactValidArgs(int)
- the command will report an error if there are not exactly N positional args OR if there are any positional args that are not in theValidArgs
field ofCommand
RangeArgs(min, max)
- the command will report an error if the number of args is not between the minimum and maximum number of expected args.
var cmd = &cobra.Command{ Short: "hello", Args: cobra.MinimumNArgs(1), // 使用内置的验证函数 Run: func(cmd *cobra.Command, args []string) { fmt.Println("Hello, World!") }, }
也可自定义验证函数:
var cmd = &cobra.Command{ Short: "hello", // Args: cobra.MinimumNArgs(10), // 使用内置的验证函数 Args: func(cmd *cobra.Command, args []string) error { // 自定义验证函数 if len(args) < 1 { return errors.New("requires at least one arg") } if myapp.IsValidColor(args[0]) { return nil } return fmt.Errorf("invalid color specified: %s", args[0]) }, Run: func(cmd *cobra.Command, args []string) { fmt.Println("Hello, World!") }, }
Help
cmd.SetHelpCommand(cmd *Command) cmd.SetHelpFunc(f func(*Command, []string)) cmd.SetHelpTemplate(s string)
后面两个用于任何子命令。
Usage
cmd.SetUsageFunc(f func(*Command) error) cmd.SetUsageTemplate(s string)
参考:
1. grpc+grpc-gateway 「连载二」Hello World
2. https://gitee.com/yuxio/edgecontroller/blob/master/cnca/cmd/cnca.go