前言
最近应为一直在写 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()
完整代码
https://play.golang.org/p/mjgZ6SJMeAm
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
https://github.com/alecthomas…
一些主要的特点:
- 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 社区最大的魅力。