二丫讲梵 二丫讲梵
首页
  • 最佳实践
  • 迎刃而解
  • Nginx
  • Php
  • Zabbix
  • AWS
  • Prometheus
  • Grafana
  • CentOS
  • Systemd
  • Docker
  • Rancher
  • Ansible
  • Ldap
  • Gitlab
  • GitHub
  • Etcd
  • Consul
  • RabbitMQ
  • Kafka
  • MySql
  • MongoDB
  • OpenVPN
  • KVM
  • VMware
  • Other
  • ELK
  • K8S
  • LLM
  • Nexus
  • Jenkins
  • 随写编年
  • 家人物语
  • 追忆青春
  • 父亲的朋友圈
  • 电影音乐
  • 效率工具
  • 博客相关
  • Shell
  • 前端实践
  • Vue学习笔记
  • Golang学习笔记
  • Golang编程技巧
  • 学习周刊
  • Obsidian插件周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • json2go (opens new window)
    • gopher (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)

二丫讲梵

行者常至,为者常成
首页
  • 最佳实践
  • 迎刃而解
  • Nginx
  • Php
  • Zabbix
  • AWS
  • Prometheus
  • Grafana
  • CentOS
  • Systemd
  • Docker
  • Rancher
  • Ansible
  • Ldap
  • Gitlab
  • GitHub
  • Etcd
  • Consul
  • RabbitMQ
  • Kafka
  • MySql
  • MongoDB
  • OpenVPN
  • KVM
  • VMware
  • Other
  • ELK
  • K8S
  • LLM
  • Nexus
  • Jenkins
  • 随写编年
  • 家人物语
  • 追忆青春
  • 父亲的朋友圈
  • 电影音乐
  • 效率工具
  • 博客相关
  • Shell
  • 前端实践
  • Vue学习笔记
  • Golang学习笔记
  • Golang编程技巧
  • 学习周刊
  • Obsidian插件周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • json2go (opens new window)
    • gopher (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)
  • Shell编程

  • Go编程笔记

    • 开发技巧

    • 库包研究

      • 使用gorm进行联合查询的整理总结
      • 一个ftp客户端的封装
      • 使用go-bindata将文件编译进二进制
      • go-gitlab包源码探寻与心得
      • 利用cobra库快速开发类似kubectl一样的命令行工具
      • 使用MongoDB官方go库操作MongoDB
      • 再探-利用gorm自身提供的方法实现MySQL中数据关联的能力
      • 使用retry-go给项目添加重试机制
      • go-cache包的使用简析
      • 利用gorm自身提供的方法实现存在更新不存在则创建的能力
      • 近期关于cobra库的一些实践心得总结
        • 打印版本
          • 效果展示
          • 代码定义
          • 构建传递
        • 环境变量
        • 生成文档
    • 个人项目

  • 前端编程笔记

  • Go学习笔记

  • Vue-21年学习笔记

  • Vue-22年重学笔记

  • 编程世界
  • Go编程笔记
  • 库包研究
二丫讲梵
2025-04-12
目录

近期关于cobra库的一些实践心得总结

文章发布较早,内容可能过时,阅读注意甄别。

开源社区中,go 语言的 cli 库有很多,有名的也不少,个人觉得不需要都熟练,只需要针对一个用的熟就可以了,其他的稍作了解即可。

最近在实际开发工作中,针对 cobra 库又有了一些实践心得,这里做一个汇总记录,以后如果有新的内容,也会更新在这里。

# 打印版本

一个开源项目,以及二进制,自身附带详细的版本信息,还是非常重要的,这有助于用户了解自己当前所用的版本,也方便在问题反馈时,提供使用的版本号。

# 效果展示

在 cobra 中,版本信息可以通过自定义模板进行打印,我这里做出来的效果如下:

$ eryajfctl -v
🍉 eryajfctl version information: 
  Version:    v0.1.0
  Git Commit: a0ea03e
  Go version: go1.24.2
  OS/Arch:    linux/amd64
  Build Time: 2025-04-10 09:50:37
1
2
3
4
5
6
7

想要实现这个效果,需要两块儿地方协同定义。

# 代码定义

代码中的定义:

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "eryajfctl",
	Short: "eryajf goscript",
	Long:  `🦄 利用cobra制作运维日常工具箱的框架。`,
	Run: func(cmd *cobra.Command, args []string) {
		// 检查是否有 -v 参数
		if versionFlag, _ := cmd.Flags().GetBool("version"); versionFlag {
			fmt.Println(cmd.VersionTemplate())
			return
		}
		cmd.Help()
	},
}

var (
	Version   string
	GitCommit string
	BuildTime string
)

func init() {
	rootCmd.Version = Version
	rootCmd.SetVersionTemplate(fmt.Sprintf(`🍉 {{with .Name}}{{printf "%%s version information: " .}}{{end}}
  {{printf "Version:    %%s" .Version}}
  Git Commit: %s
  Go version: %s
  OS/Arch:    %s/%s
  Build Time: %s

`, GitCommit, runtime.Version(), runtime.GOOS, runtime.GOARCH, BuildTime))
	rootCmd.Flags().BoolP("version", "v", false, "Show version information")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

简单做一下说明:

  1. SetVersionTemplate 是 cobra 提供的一个定义版本模板的方法,可以调用并定义版本的格式及内容。这里的格式:反引号内定义的,和最终打印出来的是一样的,所以你要调整最终打印的效果,只需要调整反引号里边的排版即可。
  2. 在运行命令处,默认情况下是打印帮助信息,在这个打印之前,需要先判断是否传递了-v 参数,如果传递,则打印版本信息并退出。
  3. 版本号中打印的一些信息,取值于运行时的一些信息,这些信息,通过二进制构建时 -ldflags 传递。这块儿,我一般放到 Makefile 中定义。

# 构建传递

通常,我会在 Makefile 中做如下定义:

# 定义项目名称
BINARY_NAME=eryajfctl

# 定义输出目录
OUTPUT_DIR=bin

VERSION    = $(shell git describe --tags --always)
GIT_COMMIT = $(shell git rev-parse --short HEAD)
BUILD_TIME = $(shell date "+%F %T")

define LDFLAGS
"-X 'github.com/eryajf/eryajfctl/cmd.Version=${VERSION}' \
 -X 'github.com/eryajf/eryajfctl/cmd.GitCommit=${GIT_COMMIT}' \
 -X 'github.com/eryajf/eryajfctl/cmd.BuildTime=${BUILD_TIME}'"
endef

.PHONY: build
build:
	go build -ldflags=${LDFLAGS} -o ${BINARY_NAME} main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这里需要注意一点:定义 LDFLAGS 时,传递的 github.com/eryajf/eryajfctl/cmd 就是上边代码定义所处的包位置,这里一定要对应上,否则可能不会生效。

通常通过 make build 构建出来二进制,执行之后,就能看到对应的版本信息了。

# 环境变量

日常使用时,一般变量都是通过传参,或者配置文件定义,那么,是否有比较优雅的姿势,能够再兼容从环境变量中读取呢。

CNB (opens new window) 的插件 (opens new window)是以镜像的形式提供服务,并且所有的参数通过环境变量传递,我做了一些实践,社区提交的首个插件 doge-cdn-refresh (opens new window)。

实现思路大概如下:


func init() {
	viper.AutomaticEnv()
	viper.SetEnvPrefix("PLUGIN")
	viper.BindEnv("ename") // 绑定环境变量 PLUGIN_ENAME
	viper.BindEnv("epass") // 绑定环境变量 PLUGIN_EPASS

	rootCmd.AddCommand(exCmd)
	exCmd.AddCommand(ex.GetConfigCmd)
	ex.GetConfigCmd.Flags().StringP("ename", "n", viper.GetString("ename"), "传入要打印的内容 [$PLUGIN_ENAME]")
	viper.BindPFlag("ename", ex.GetConfigCmd.Flags().Lookup("ename"))
	ex.GetConfigCmd.Flags().StringP("epass", "p", viper.GetString("epass"), "传入要打印的内容 [$PLUGIN_EPASS")
	viper.BindPFlag("epass", ex.GetConfigCmd.Flags().Lookup("epass"))
	// 遍历所有 flags,清除默认值占位符,避免在日志中打印
	ex.GetConfigCmd.Flags().VisitAll(func(f *pflag.Flag) {
		f.DefValue = ""
	})
	ex.GetConfigCmd.PreRunE = func(cmd *cobra.Command, args []string) error {
		if viper.GetString("ename") == "" {
			return fmt.Errorf("必须通过环境变量 PLUGIN_ENAME 或命令行参数 --ename 提供值")
		}
		if viper.GetString("epass") == "" {
			return fmt.Errorf("必须通过环境变量 PLUGIN_EPASS 或命令行参数 --epass 提供值")
		}
		return nil
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

这里有几个注意点:

  1. 首先利用 viper 读取环境变量,然后把读取的值放到参数的默认值中。
  2. 同时再通过 viper.BindPFlag 将命令行参数的值赋值给 viper,这么做的目的,是为了便于做下边的校验,也就是用户无论是通过环境变量,还是通过参数,必须传递 ename 才能正常执行 ex.GetConfigCmd 的逻辑,否则就提示用户输出该参数。
  3. 还有非常重要的一项,是把所有参数的默认值的输出,给去掉,这个细节很重要,不能因为 a 参数没有传递,就把 b 参数传递的值给暴漏了,如下是对比。第一次执行是未做默认值隐藏,会把密码打印到终端,第二次执行则把默认值隐藏掉了,就不会打印了。 My_Photor_1744467602931.webp

这里为什么使用viper获取环境变量而不是直接使用os.Getenv()呢?

  1. 首先viper是cobra官方提供的,功能更强大,和cobra的融合也非常好,使用很方便。
  2. 很重要的一点,vipre在获取环境变量的时候,还支持识别变量的类型,从而直接传递给cobra使用。上边例子中都是string类型,比如下边还可以改成[]string类型:
	// 首先从环境变量中获取配置
	viper.AutomaticEnv()
	viper.SetEnvPrefix("PLUGIN")
	viper.BindEnv("urls")
	rootCmd.Flags().StringSliceP("urls", "u", strings.Split(viper.GetString("urls"), ","), "Refresh URLs [$PLUGIN_URLS]")
1
2
3
4
5

# 生成文档

cobra 提供了自动生成命令行工具文档的能力,只需添加如下代码定义:

func init() {
	rootCmd.Flags().BoolVarP(&MarkdownDocs, "md-docs", "m", false, "gen Markdown docs")
}

var MarkdownDocs bool

func GenDocs() {
	if MarkdownDocs {
		if err := doc.GenMarkdownTree(rootCmd, "./docs"); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后把 GenDocs() 放到 main 函数中,通过执行:go run main.go -m 就会自动在 docs 目录下生成这个命令行工具的使用指南,包括每个参数的方法定义。

My_Photor_1744467987816.webp

文档还自动生成了不同参数的独立文档,可通过超链接跳转查看。

以上,是目前个人在实际开发过程中遇到的一些功能点的实践心得,事实上我还想增加一个自动生成配置文件的能力,目前需求不强,还没有深入探索,如果有这块儿实践心得的同学,欢迎评论区交流。

微信 支付宝
上次更新: 2025/04/15, 17:20:58
利用gorm自身提供的方法实现存在更新不存在则创建的能力
给我一个URL,我能将你关心的页面元素截图发给机器人

← 利用gorm自身提供的方法实现存在更新不存在则创建的能力 给我一个URL,我能将你关心的页面元素截图发给机器人→

最近更新
01
从赵心童世锦赛夺冠聊聊我的斯诺克情缘
05-16
02
学习周刊-总第211期-2025年第20周
05-15
03
记录二五年五一之短暂回归家庭
05-09
更多文章>
Theme by Vdoing | Copyright © 2017-2025 | 点击查看十年之约 | 浙ICP备18057030号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式