前言
最近应为一直在写Go,避免不了要处理一些命令行参数和配置文件。虽然Go原生的flag库比起其他语言,在处理命令行参数上已经做的很易用了,Go的社区也有很多好用的库。这篇文章主要介绍一下自己这段时间接触使用过库,为有同样需求的朋友也提供一些参考。
flag
首先还是有必要简单介绍一下Go的原生库flag, 直接上代码
基本用法
var id = flag.Int("id", 1, "user id")
var mail = flag.String("mail", "test@gmail.com", "mail")
var help = flag.Bool("h", false, "this help")
也可以用指针变量去接收flag
var name string
flag.StringVar(&name, "name", "leeif", "your name")
变量也可以是一个实现flag.Value接口的结构体
type Address struct {
s string
}
func (a *Address) String() string {
return a.s
}
func (a *Address) Set(s string) error {
if s == "" {
return errors.New("address can't be empty")
}
a.s = s
return nil
}
ad := Address{}
flag.Var(&ad, "address", "address of the server")
解析
flag.Parse()
flagSet可以用来处理subcommand
upload := flag.NewFlagSet("upload", flag.ContinueOnError)
localFile := upload.Bool("localFile", false, "")
download := flag.NewFlagSet("download", flag.ContinueOnError)
remoteFile := download.Bool("remoteFile", false, "")
switch os.Args[1] {
case "upload":
if err := upload.Parse(os.Args[2:]); err == nil {
fmt.Println("upload", *localFile)
}
case "download":
if err := download.Parse(os.Args[2:]); err == nil {
fmt.Println("download", *remoteFile)
}
}
命令行的指定形式。
-flag (也可以是--flag)
-flag=x
-flag x // non-boolean flags only
原生的flag在简单的需求下,已经够用了,但是想构建一些复杂的应用的时候还是有些不方便。然而flag的可扩展性也衍生了许多各具特色的第三方库。
kingpin
一些主要的特点:
- fluent-style的编程风格
- 不仅可以解析flag, 也可以解析非flag参数
- 支持短参数的形式
- sub command
一般的使用方法
debug = kingpin.Flag("debug", "Enable debug mode.").Bool()
// 可被环境变量覆盖的flag
// Short方法可以指定短参数
timeout = kingpin.Flag("timeout", "Timeout waiting for ping.").Default("5s").OverrideDefaultFromEnvar("PING_TIMEOUT").Short('t').Duration()
// IP类型的参数
// Required参数为必须指定的参数
ip = kingpin.Arg("ip", "IP address to ping.").Required().IP()
count = kingpin.Arg("count", "Number of packets to send").Int()
用指针类型接收flag
var test string
kingpin.Flag("test", "test flag").StringVar(&test)
实现kingpin.Value接口的参数类型
type Address struct {
s string
}
func (a *Address) String() string {
return a.s
}
func (a *Address) Set(s string) error {
if s == "" {
return errors.New("address can't be empty")
}
a.s = s
return nil
}
ad := Address{}
kingpin.Flag("address", "address of the server").SetValue
解析
kingpin.Parse()
使用sub command
var (
deleteCommand = kingpin.Command("delete", "Delete an object.")
deleteUserCommand = deleteCommand.Command("user", "Delete a user.")
deleteUserUIDFlag = deleteUserCommand.Flag("uid", "Delete user by UID rather than username.")
deleteUserUsername = deleteUserCommand.Arg("username", "Username to delete.")
deletePostCommand = deleteCommand.Command("post", "Delete a post.")
)
func main() {
switch kingpin.Parse() {
case deleteUserCommand.FullCommand():
case deletePostCommand.FullCommand():
}
}
kingpin会自动生成help文案。不用做任何设置用--help即可查看。-h则需要手动配置。
kingpin.HelpFlag.Short('h')
cobra
https://github.com/spf13/cobra
cobra是go程序员必须要知道的一款命令行参数库。很多大的项目都是用cobra搭建的。
cobra是为应用级的命令行工具而生的项目, 不仅提供了基本的命令行处理功能外, 而提供了一套搭建命令行工具的架构。
cobra的核型架构。
▾ appName/
▾ cmd/
root.go
sub.go
main.go
所有的命令行配置分散写在各个文件中, 例如 root.go
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
func init() {
rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/")
}
var rootCmd = &cobra.Command{
Use: "hugo",
Short: "Hugo is a very fast static site generator",
Long: `A Fast and Flexible Static Site Generator built with
love by spf13 and friends in Go.
Complete documentation is available at http://hugo.spf13.com`,
Run: func(cmd *cobra.Command, args []string) {
// Do Stuff Here
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
sub.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(subCmd)
}
var subCmd = &cobra.Command{
Use: "sub command",
Short: "short description",
Long: `long description`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("sub command")
},
}
在最外面的main.go里,只用写一句话。
package main
import (
"{pathToYourApp}/cmd"
)
func main() {
cmd.Execute()
}
用cobra的架构来搭建命令行工具会使架构更清晰。
viper
https://github.com/spf13/viper
viper使用来专门处理配置文件的工具, 因为作者和cobra的作者是同一个人, 所以经常和cobra一起配合着使用。就连cobra的官方说明里也
提到了viper。
viper.SetConfigName("config") // name of config file (without extension)
viper.AddConfigPath("/etc/appname/") // path to look for the config file in
viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search paths
viper.AddConfigPath(".") // optionally look for config in the working directory
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
获取读取到的参数, 为map[string]interface{}类型。
c := viper.AllSettings()
viper也提供处理flag的功能,但是个人感觉没有上面两个库好用,这里也就不做介绍了。
kiper
往往我们要同时处理命令行参数和配置文件, 并且我们想合并这两种参数。
虽然可以用cobra+viper可以实现, 但是个人喜欢kingpin, 因为kingpin可以检查参数的正确性(通过实现kingpin.Value接口的数据类型)。
于是自己写了一个kingpin+viper的wrapper工具, kiper。
https://github.com/leeif/kiper
主要特点:
- 通过tag配置flag设定(kingpin)
- 通过viper读取配置文件
- 自动合并flag和配置文件参数
具体用法
package main
import (
"errors"
"fmt"
"os"
"strconv"
"github.com/leeif/kiper"
)
type Server struct {
Address *Address `kiper_value:"name:address"`
Port *Port `kiper_value:"name:port"`
}
type Address struct {
s string
}
func (address *Address) Set(s string) error {
if s == "" {
return errors.New("address can't be empty")
}
address.s = s
return nil
}
func (address *Address) String() string {
return address.s
}
type Port struct {
p string
}
func (port *Port) Set(p string) error {
if _, err := strconv.Atoi(p); err != nil {
return errors.New("not a valid port value")
}
port.p = p
return nil
}
func (port *Port) String() string {
return port.p
}
type Config struct {
ID *int `kiper_value:"name:id;required;default:1"`
Server Server `kiper_config:"name:server"`
}
func main() {
// initialize config struct
c := &Config{
Server: Server{
Address: &Address{},
Port: &Port{},
},
}
// new kiper
k := kiper.NewKiper("example", "example of kiper")
k.SetConfigFileFlag("config", "config file", "./config.json")
k.Kingpin.HelpFlag.Short('h')
// parse command line and config file
if err := k.Parse(c, os.Args[1:]); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(c.Server.Port)
fmt.Println(*c.ID)
}
配置文件需要和Config结构体保持一致。
config.json
{
"server": {
"address": "192.0.0.1",
"port": "8080"
},
"id": 2
}
有待改善的地方
- 现在还没有做sub command的功能。
- 合并的时候配置文件总会覆盖命令行参数(合并的优先顺序)
总结
Go社区给开发着提供了多种处理命令行参数和配置文件的工具。每种工具都有各自的特点和应用场景。 例如flag是原生支持,扩展性高。kingpin可以检查参数的正确性。cobra适合构建复杂的命令行工具。开发者可以根据自己搭需求选择使用的工具, 这样的可选性和自由度也正是Go社区最大的魅力。