本文使用 cobra 库实现一个命令行工具,类似 git、docker、kubectl 这类的工具。
本文仅为一个初具模型的示例,但有实践参考意义。

起因

在编程中,很多时候,程序都会处理多个参数,特别是一些工具类的函数,需要整合较多功能,即使同一功能,也会有不同参数,利用配置文件或命令选项方式,可使程序具备通用性,也具扩展性。

简单介绍

cobra 功能较强大,在 golang 生态中有很多应用,如大名鼎鼎的 docker。其支持子命令执行,配置文件读写等,本文以实战为目的,不过多介绍。

整体结构

工程名为 cmdtool,见名知义。
工程目录及对应介绍如下:

.
├── cmd ## 子命令总目录
│   ├── db    ## 子命令1实现目录
│   ├── misc  ## 子命令2实现目录
│   ├── rootCmd.go  ## 子命令入口
│   └── test  ## 子命令3实现目录
├── common ## 共用函数、变量
│   ├── conf
│   ├── constants
│   └── globalfunc.go
├── config.yaml ## 配置文件
├── go.mod
├── go.sum
├── main.go  ## 入口函数
├── mybuild.sh ## 编译脚本
├── pkg  ## 库
│   ├── com
│   └── wait
├── README
└── vendor ## 依赖库├── github.com├── golang.org├── gopkg.in├── k8s.io└── xorm.io

其中 cmd 是所有子命令的入口目录,不同子命令,以不同子目录形式存在。common 目录存在共用的变量或初始化函数,等等。pkg 为个人总结积累的一些有用的库。
main.go 为主函数,调用了 cmd/rootCmd.go 的创建命令函数,由此进入cobra的处理框架中。
一般情况下,只需要扩展 cmd 目录下子命令,并补充 rootCmd.go 函数即可,其它即为业务程序的处理。

工程分解

入口函数

主入口函数非常简单,实际调用了 rootCmd.go 中的执行函数。

package mainimport (_ "fmt""os"rootCmd "github.com/latelee/cmdtool/cmd"
)func main() {if err := rootCmd.Execute(); err != nil {os.Exit(1)}
}

命令行入口

rootCmd.go 源码:

package cmdimport ("os""bytes""path/filepath""github.com/spf13/cobra""github.com/spf13/viper""github.com/fsnotify/fsnotify""k8s.io/klog"test "github.com/latelee/cmdtool/cmd/test"misc "github.com/latelee/cmdtool/cmd/misc"db   "github.com/latelee/cmdtool/cmd/db"conf "github.com/latelee/cmdtool/common/conf"
)var (longDescription = `  database test tool.命令终端测试示例工具。
`example = `  comming soon...
`
)var cfgFile stringvar rootCmd = &cobra.Command{Use:   filepath.Base(os.Args[0]),Short: "database tool",Long: longDescription,Example: example,Version: "1.0",
}func Execute() error {rootCmd.AddCommand(test.NewCmdTest())rootCmd.AddCommand(misc.NewCmdMisc())rootCmd.AddCommand(db.NewCmdDb())return rootCmd.Execute()
}func init() {cobra.OnInitialize(initConfig)rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (config.yaml)")rootCmd.PersistentFlags().BoolVar(&conf.FlagPrint, "print", false, "will print sth")}var yamlExample = []byte(
`dbserver:dbstr: hellooooootimeout:connect: 67ssingleblock: 2sname:name: firstblood
`)func initConfig() {if cfgFile != "" {viper.SetConfigFile(cfgFile)} else {viper.AddConfigPath("./")viper.SetConfigName("config")viper.SetConfigType("yaml")}viper.AutomaticEnv()err := viper.ReadInConfig();if  err != nil {klog.Println("not found config file. using default")viper.ReadConfig(bytes.NewBuffer(yamlExample))viper.SafeWriteConfig()}conf.FlagDBServer = viper.GetString("dbserver.dbstr")conf.FlagTimeout = viper.GetString("dbserver.timeout.connect")conf.FlagName = viper.GetString("dbserver.name.name")klog.Println(conf.FlagDBServer, conf.FlagTimeout, conf.FlagName)//设置监听回调函数viper.OnConfigChange(func(e fsnotify.Event) {conf.FlagTimeout = viper.GetString("dbserver.timeout.connect")})viper.WatchConfig()}

其中 initConfig 函数作用是读取配置文件字段,如果没有文件则自动生成默认的配置。注意,该函数的 yamlExample 需要保持实际配置文件的格式(从 viper.GetString 函数参数可以看出 dbserver 为顶层字段)。
最后利用 viper 监听配置文件的变化。实际测试发现会触发2次,利用循环定时判断变量值可以解决。

子命令实现

子命令的实现形式大同小异,以 test 为例,源码如下:

package cmdimport ("github.com/spf13/cobra"_ "github.com/spf13/pflag""k8s.io/klog"
)var (name = `test`shortDescription = `  test command`longDescription  = `  test...
`example = `  example comming up...
`
)type UserCmdFunc struct {name stringfn func(args []string)
}func NewCmdTest() *cobra.Command{var cmd = &cobra.Command{Use:     name,Short:   shortDescription,Long:    longDescription,Example: example,RunE: func(cmd *cobra.Command, args []string) error {if (len(args) == 0) {klog.Warning("no args found")return nil}if (args[0] == "foo"){foo(args)} else if (args[0] == "watch"){testWatch(args)} else {klog.Printf("cmd '%v' not support", args[0])return nil} return nil},}return cmd
}

在 NewCmdTest 函数中创建 cobra.Command 并返回,在 RunE 中判断参数并真正执行业务函数。本例实现了参数监听功能,源码:

// 监听配置参数变化
func testWatch(args []string) {timeout := conf.FlagTimeoutfor {if timeout != conf.FlagTimeout {klog.Printf("param changed: %v\n", conf.FlagTimeout)timeout = conf.FlagTimeout}com.Sleep(1000)}
}

当配置文件相应字段变化时,将其打印出来。

测试

默认输出帮助信息:

$ ./cmdtool.exedatabase test tool.命令终端测试示例工具。Usage:cmdtool.exe [command]Examples:comming soon...Available Commands:db            db commandhelp        Help about any commandmisc          misc commandtest          test commandFlags:-h, --help      help for cmdtool.exe--print     will print sth--version   version for cmdtool.exeUse "cmdtool.exe [command] --help" for more information about a command.

执行子命令:

$ ./cmdtool.exe test foo
[2020-10-20 21:46:39.304 rootCmd.go:113] helloooooo 61s firstblood
[2020-10-20 21:46:39.305 busy.go:12] test foo.....

监听配置文件:

$ ./cmdtool.exe test watch
[2020-10-20 21:47:14.408 rootCmd.go:113] helloooooo 61s firstblood
[2020-10-20 21:47:29.411 busy.go:20] param changed: 100s

源码

源码在此。

其它事项

利用viper.SafeWriteConfig()写配置文件时,发现 yamlExample 添加的注释会被删除,所以可以考虑直接将字符串通过ioutil.WriteFile写到文件。

Golang实践录:命令行cobra库实例相关推荐

  1. Golang实践录:命令行cobra库实例再三优化

    本文是上一文章<Golang实践录:命令行cobra库实例优化> 的优化,主要的子命令的业务实现的整理. 起因 上一版本实现的方式,还是有点不满意,格式也不对齐,重要的是,似乎不是正规的方 ...

  2. Golang实践录:命令行cobra库实例优化

    本文上一文章<Golang实践录:命令行cobra库实例> 的优化,主要的子命令的业务实现的整理. 起因 旧版本中,每个子命令的入口函数,均需一一判断传入参数,并调用对应的业务实现函数,编 ...

  3. golang go get 命令行安装库 报错 go: cannot use path@version syntax in GOPATH mode 解决方法

    go mod作为官方的依赖管理工具,类似于maven这种本地缓存库的管理方式,其主要是通过GOPATH/pkg/mod下的缓存包来对工程进行构建. 问题: 执行go get github.com/go ...

  4. 基于Golang的CLI 命令行程序开发

    基于Golang的CLI 命令行程序开发 [阅读时间:约15分钟] 一. CLI 命令行程序概述 二. 系统环境&项目介绍&开发准备 1.系统环境 2.项目介绍 3.开发准备 三.具体 ...

  5. python脚本实例手机端-python链接手机用Python实现命令行闹钟脚本实例

    前言: 这篇文章给大家介绍了怎样用python创建一个简单的报警,它可以运行在命令行终端,它需要分钟做为命令行参数,在这个分钟后会打印"wake-up"消息,并响铃报警,你可以用0 ...

  6. ECS(Linux)连接RDS,使用命令行方式连接实例

    使用命令行方式连接实例 通过命令行连接RDS MySQL数据库,连接方式如下: mysql -h<连接地址> -P<端口> -u<用户名> -p -D<数据库 ...

  7. 常用的python命令行解析库

    常用的python命令行解析库,这儿介绍3种: 1.argparse 2.click 3.fire argparse是python自带的模块,要经历解析器初始化.参数定义.解析一套流程,使用起来有些繁 ...

  8. 控制台发送get命令_.NET Core使用命令行参数库构建控制台应用程序

    前言 在我们开发中可能需要设计一次性应用程序,这些实用程序可以利用接近原始源代码的优势,但可以在与主Web应用程序完全独立的安全性上下文中启动.具体在 [管理过程](https://12factor. ...

  9. php shell,php命令行写shell实例详解

    php 可以像java perl python 那样运行,今天发现如果我早早知道这个,或许我不会去学习java 和 python 当年学java不过为了一个程序放在服务器上,不停的跑啊跑,原来 php ...

最新文章

  1. Nodejs+Express学习二(Mongoose基础了解)
  2. 感谢武汉晚报的采访报道:清华硕士回襄阳老家当“威客” 两年赚30万元
  3. cannot和can not的区别 666666
  4. linux fmt命令
  5. leetcode50. Pow(x, n)(快速幂)
  6. jq之省市区级联插件
  7. 2345天气王怎么查看历史天气 2345天气王如何查看历史天气
  8. psutil python库
  9. 修改pip安装源加快python模块安装
  10. 如何在JAVA编程语言程序开发中更好的利用数据库中2两张表?
  11. 用c语言编写一个打勾的图形,C语言图形编程.ppt
  12. 设计模式-23种设计模式
  13. 磁盘转换|如何将mbr转换成gpt?
  14. 运行时错误‘53’:文件未找到:MathPage.WLL
  15. 华为云PB级数据库GaussDB(for Redis)揭秘第八期:用高斯 Redis 进行计数
  16. 几何画板常见问题解决方案
  17. Canvas画各种线
  18. WRF嵌套网格的设计
  19. 蒸汽朋克与游戏的结合————《机械迷城》
  20. Java抽象类,接口练习之猫狗案例加入跳高功能分析及其代码实现

热门文章

  1. MFC开发IM-第十八篇、如何设置编辑框的内容
  2. 优化网页代码提高网页访问速度
  3. iOS 16要来了:速度更快、UI改动明显?苹果WWDC大会或将在线下举行
  4. 库克警告员工不要泄露公司信息:将全力追查
  5. 2021年Q2全球智能手机销量小米升至第二,苹果降至第三
  6. IBM 2nm芯片弯道超车了?他连车都没有
  7. 1777亿重罚,苹果瑟瑟发抖!
  8. 理想更新“货车并线预警”遭用户吐槽 李想:仍在优化
  9. 中兴通讯发布《5G上行增强技术白皮书》:深化多频段协同能力
  10. 完全“撞脸”今日头条,这家店火了!已被今日头条起诉商标侵权...