分享
  1. 首页
  2. 文章

[Golang] 聊一聊Go的那些处理命令行参数和配置文件的库

leeif · · 2980 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

前言

最近应为一直在写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社区最大的魅力。


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
2980 次点击
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏