惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
S
Securelist
博客园 - Franky
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
IT之家
IT之家
GbyAI
GbyAI
Microsoft Azure Blog
Microsoft Azure Blog
The Cloudflare Blog
云风的 BLOG
云风的 BLOG
N
News and Events Feed by Topic
AI
AI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Schneier on Security
Schneier on Security
Attack and Defense Labs
Attack and Defense Labs
Vercel News
Vercel News
腾讯CDC
Google DeepMind News
Google DeepMind News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
M
MIT News - Artificial intelligence
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
量子位
S
Schneier on Security
Hacker News: Ask HN
Hacker News: Ask HN
Cyberwarzone
Cyberwarzone
S
Security Affairs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News and Events Feed by Topic
T
Tenable Blog
PCI Perspectives
PCI Perspectives
MyScale Blog
MyScale Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
W
WeLiveSecurity
N
News | PayPal Newsroom
P
Proofpoint News Feed
O
OpenAI News
C
CERT Recently Published Vulnerability Notes
B
Blog
Cisco Talos Blog
Cisco Talos Blog
Microsoft Security Blog
Microsoft Security Blog
V
Visual Studio Blog
MongoDB | Blog
MongoDB | Blog
大猫的无限游戏
大猫的无限游戏
A
Arctic Wolf
Y
Y Combinator Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Spread Privacy
Spread Privacy

二丫讲梵

学习周刊-总第258期-2026年第15周 学习周刊-总第257期-2026年第14周 学习周刊-总第256期-2026年第13周 学习周刊-总第255期-2026年第12周 学习周刊-总第254期-2026年第11周 学习周刊-总第253期-2026年第10周 临时插播一条羊毛,免费领取450元大模型API代金券 学习周刊-总第252期-2026年第09周 学习周刊-总第251期-2026年第08周 学习周刊-总第250期-2026年第07周 学习周刊-总第249期-2026年第06周 诚邀评论,聊聊你所知欲知的我 学习周刊-总第248期-2026年第05周 我的QQ动态之2015年 我的QQ动态之2014年 我的QQ动态之2013年 我的QQ动态之2012年 我的QQ动态-2010-2011年 我的QQ动态-创栏小叙 学习周刊-总第247期-2026年第04周 Nexus社区版权益阉割--一文告诉你有哪些版本可以选择 学习周刊-总第246期-2026年第03周 学习周刊-总第245期-2026年第02周 学习周刊-总第244期-2026年第01周 学习周刊-总第243期-2025年第52周 学习周刊-总第242期-2025年第51周 开源项目ZenOps:带你领略禅意运维 学习周刊-总第241期-2025年第50周 用京东金融,享负债人生 学习周刊-总第240期-2025年第49周 学习周刊-总第239期-2025年第48周 整理我在静态服务透明代理上的极致求索之路,最后一个你绝想不到 学习周刊-总第238期-2025年第47周 我们输了,但收获颇多 太多了,太多了 学习周刊-总第237期-2025年第46周 学习周刊-总第236期-2025年第45周 学习周刊-总第235期-2025年第44周 二五年国庆二三事 学习周刊-总第234期-2025年第43周 学习周刊-总第233期-2025年第42周 学习周刊-总第232期-2025年第41周 学习周刊-总第231期-2025年第40周 学习周刊-总第230期-2025年第39周 西湖毅行 2025年开源世界逸闻三则 学习周刊-总第229期-2025年第38周 手抄《与妻书》 CNB开发与构建基于docker-cache缓存复用的配置实践心得 学习周刊-总第228期-2025年第37周 父亲善行录 学习周刊-总第227期-2025年第36周 带你认识我之看看我的高中同学录(其二) 学习周刊-总第226期-2025年第35周 带你认识我之看看我的高中同学录(其一) 认识神级MCP工具系列--用anyquery和数据库交互 学习周刊-总第225期-2025年第34周 学习周刊-总第224期-2025年第33周 学习周刊-总第223期-2025年第32周 学习周刊-总第222期-2025年第31周 学习周刊-总第221期-2025年第30周 CNB云原生开发环境届的瑞士军刀,详解qifei项目 vuepress-vdoing主题配置自建不蒜子统计 学习周刊-总第220期-2025年第29周 写在博客发表文章1000篇的节点 从claude cli的体验聊聊最大的敌人是我们自己的成见 学习周刊-总第219期-2025年第28周 学习周刊-总第218期-2025年第27周 学习周刊-总第217期-2025年第26周 理论正确,事实错误 学习周刊-总第216期-2025年第25周 学习周刊-总第215期-2025年第24周 学习周刊-总第214期-2025年第23周 学习周刊-总第213期-2025年第22周 学习周刊-总第212期-2025年第21周 从赵心童世锦赛夺冠聊聊我的斯诺克情缘 学习周刊-总第211期-2025年第20周 记录二五年五一之短暂回归家庭 学习周刊-总第210期-2025年第19周 学习周刊-总第209期-2025年第18周 学习周刊-总第208期-2025年第17周 Go开发实践之Gin框架将前端的dist目录embed到二进制 学习周刊-总第207期-2025年第16周 学习周刊-总第206期-2025年第15周 我的嗜好--嗑瓜子 记大宝参加幼儿园组织的清明烈士陵园扫墓活动 学习周刊-总第205期-2025年第14周 学习周刊-总第204期-2025年第13周 前端开发小笔记--在数组中给字段添加值与删除字段的操作 学习周刊-总第203期-2025年第12周 人生实苦,何以自渡 学习周刊-总第202期-2025年第11周 近期发现的一些优秀的工具提名(一) 学习周刊-总第201期-2025年第10周 学习周刊-总第200期-2025年第09周 记一次女友喝醉使我忍术破功 爱情风波--一次分手又复合的经历 学习周刊-总第199期-2025年第08周 整理我的信息源 学习周刊-总第198期-2025年第07周
近期关于cobra库的一些实践心得总结
二丫讲梵 · 2025-04-13 · via 二丫讲梵

开源社区中,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

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

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