Golang中如何使用CLI参数和配置文件来赋值

Golang中如何使用CLI参数和配置文件来赋值 我有一个工具,希望实现以下功能:

  1. 查找默认配置文件(已完成)
  2. 将数据解组到结构体的字段中(已完成)
  3. 使用 flag 包解析所有命令行参数(已完成)
  4. 如果参数值不等于结构体字段值,则相应地更新字段
  5. 或者,也许我应该完全重新考虑这个方法 🙂

以下是一些代码片段:

type Config struct {
	User    string `json:"user"`
	Port    int    `json:"port"`
	Key     string `json:"key"`
	Nodes   string `json:"nodes"`
	Syslog  bool   `json:"syslog"`
	LogPath string `json:"logpath"`
	Fatal   bool   `json:"fatal"`
}

// 这是我原以为最佳方法的样板代码
func (c *Config) Rectify(uname string) {
	c.User = uname
}


func getConfig(fd string) {
	contents, e := ioutil.ReadFile(fd)
	check(e)
	e = json.Unmarshal(contents, &cfg)
	check(e)
}

func main() {
        // 解组;赋值给 cfg,此处为示例硬编码
	getConfig("/home/rxlx/.send_config.json")
	uname := flag.String("u", "send", "username")
        // 为简洁起见,省略了其他标志
	flag.Parse()
}

这就是我目前有点卡住的地方。我试图简化该工具的命令行使用方式,同时使其更符合 Go 语言的风格(或者说更地道?)。

谢谢


更多关于Golang中如何使用CLI参数和配置文件来赋值的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

看看 cobraviper

更多关于Golang中如何使用CLI参数和配置文件来赋值的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中处理CLI参数和配置文件时,可以使用flag包配合结构体反射来实现优先级覆盖。以下是完整的解决方案:

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
)

type Config struct {
	User    string `json:"user"`
	Port    int    `json:"port"`
	Key     string `json:"key"`
	Nodes   string `json:"nodes"`
	Syslog  bool   `json:"syslog"`
	LogPath string `json:"logpath"`
	Fatal   bool   `json:"fatal"`
}

var cfg Config

func getConfig(fd string) error {
	contents, err := ioutil.ReadFile(fd)
	if err != nil {
		return err
	}
	return json.Unmarshal(contents, &cfg)
}

func updateConfigFromFlags() {
	// 定义命令行标志
	userFlag := flag.String("user", cfg.User, "username")
	portFlag := flag.Int("port", cfg.Port, "port number")
	keyFlag := flag.String("key", cfg.Key, "access key")
	nodesFlag := flag.String("nodes", cfg.Nodes, "node list")
	syslogFlag := flag.Bool("syslog", cfg.Syslog, "enable syslog")
	logPathFlag := flag.String("logpath", cfg.LogPath, "log file path")
	fatalFlag := flag.Bool("fatal", cfg.Fatal, "fatal errors")

	flag.Parse()

	// 使用反射检查并更新字段
	v := reflect.ValueOf(&cfg).Elem()
	t := v.Type()

	for i := 0; i < v.NumField(); i++ {
		field := v.Field(i)
		fieldName := t.Field(i).Name

		switch fieldName {
		case "User":
			if *userFlag != cfg.User {
				field.SetString(*userFlag)
			}
		case "Port":
			if *portFlag != cfg.Port {
				field.SetInt(int64(*portFlag))
			}
		case "Key":
			if *keyFlag != cfg.Key {
				field.SetString(*keyFlag)
			}
		case "Nodes":
			if *nodesFlag != cfg.Nodes {
				field.SetString(*nodesFlag)
			}
		case "Syslog":
			if *syslogFlag != cfg.Syslog {
				field.SetBool(*syslogFlag)
			}
		case "LogPath":
			if *logPathFlag != cfg.LogPath {
				field.SetString(*logPathFlag)
			}
		case "Fatal":
			if *fatalFlag != cfg.Fatal {
				field.SetBool(*fatalFlag)
			}
		}
	}
}

func main() {
	// 1. 加载默认配置
	err := getConfig("/home/rxlx/.send_config.json")
	if err != nil {
		fmt.Printf("Error loading config: %v\n", err)
		os.Exit(1)
	}

	// 2. 解析命令行参数并更新配置
	updateConfigFromFlags()

	// 3. 使用最终配置
	fmt.Printf("Final config: %+v\n", cfg)
}

更简洁的版本使用flagVar方法:

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
)

type Config struct {
	User    string `json:"user"`
	Port    int    `json:"port"`
	Key     string `json:"key"`
	Nodes   string `json:"nodes"`
	Syslog  bool   `json:"syslog"`
	LogPath string `json:"logpath"`
	Fatal   bool   `json:"fatal"`
}

func (c *Config) UpdateFromFlags() {
	// 使用当前配置值作为flag的默认值
	flag.StringVar(&c.User, "user", c.User, "username")
	flag.IntVar(&c.Port, "port", c.Port, "port number")
	flag.StringVar(&c.Key, "key", c.Key, "access key")
	flag.StringVar(&c.Nodes, "nodes", c.Nodes, "node list")
	flag.BoolVar(&c.Syslog, "syslog", c.Syslog, "enable syslog")
	flag.StringVar(&c.LogPath, "logpath", c.LogPath, "log file path")
	flag.BoolVar(&c.Fatal, "fatal", c.Fatal, "fatal errors")
	
	flag.Parse()
}

func main() {
	cfg := &Config{}
	
	// 加载配置文件
	contents, err := ioutil.ReadFile("/home/rxlx/.send_config.json")
	if err == nil {
		json.Unmarshal(contents, cfg)
	}

	// 解析命令行参数(会覆盖配置文件中的值)
	cfg.UpdateFromFlags()

	fmt.Printf("Config: %+v\n", cfg)
}

使用第三方库github.com/spf13/viper的解决方案:

package main

import (
	"fmt"
	"github.com/spf13/viper"
)

type Config struct {
	User    string
	Port    int
	Key     string
	Nodes   string
	Syslog  bool
	LogPath string
	Fatal   bool
}

func main() {
	viper.SetConfigName(".send_config")
	viper.SetConfigType("json")
	viper.AddConfigPath("/home/rxlx")
	viper.AddConfigPath(".")

	// 设置默认值
	viper.SetDefault("user", "send")
	viper.SetDefault("port", 8080)

	// 读取配置文件
	viper.ReadInConfig()

	// 绑定命令行参数
	viper.BindPFlag("user", flag.Lookup("user"))
	viper.BindPFlag("port", flag.Lookup("port"))

	flag.Parse()

	// 自动将配置解析到结构体
	var cfg Config
	viper.Unmarshal(&cfg)

	fmt.Printf("Config: %+v\n", cfg)
}

这种方法确保了命令行参数的优先级高于配置文件,同时保持了代码的简洁性。

回到顶部