构建Go命令行程序工具链
VSole2022-08-01 10:11:53
话说不久前,需要输出一个按发布时间排序的酷Go推荐的历史文章列表,类似于这样:
于是乎,想着从GoCN上一篇一篇copy文章的标题吧,5篇下来,手眼已经不协调了,此时此刻才想起自己貌似是个码农,此情此景那必须coding一段,让代码来输出这个列表。
思路很简单,看一下GoCN文章列表的API,再看下鉴权方式(看header是通过cookie),然后就可以coding了,通过API拉取文章数据,提取需要的字段再组装成MD输出就完事了!
当准备一个main.go搞定的时候,本着对coding的敬畏,简单的思考了一下程序的可读性、拓展性,决定还是构建一个比较完整的命令行程序,如下:
主角登场
那么要实现上图的命令行提示,程序参数解析等,不借助于第三方包当然也能实现。但,前人种好的大树为哈不乘凉呢,在业界对于命令行工具的开发已经有比较成熟的实现,下面清楚本文的主角:
- cobra:应用命令行框架
- pflag:命令行参数解析
- viper:配置文件解析
下面结合实际项目来看如何使用这三位大哥来构建Go命令行程序。
先简单看下项目结构:
. ├── cmd │ ├── pull.go │ └── root.go ├── config.yaml ├── file │ └── cool-go.md ├── go.mod ├── go.sum ├── gocn-cli └── main.go
然后引入上面所推荐的三个库:
go get -u github.com/spf13/cobra@latest go get -u github.com/spf13/viper go get -u github.com/spf13/pflag
Coding
package cmd import ( "fmt" "github.com/spf13/cobra" "github.com/spf13/viper" "os" ) var ( cfgFile string ) func init() { cobra.OnInitialize(initConfig) // 解析配置文件参数 rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./config.yaml)") } // initConfig viper加载配置文件 func initConfig() { if cfgFile != "" { viper.SetConfigFile(cfgFile) } else { viper.AddConfigPath(".") viper.SetConfigName("config") } if err := viper.ReadInConfig(); err != nil { fmt.Println("Can't read config file:", err) os.Exit(1) } } // 创建全局rootCmd var rootCmd = &cobra.Command{ Use: "gocn-cli", Short: "gocn-cli is a command line tool for gocn.com", Long: "gocn-cli is a command line tool for gocn.com", Run: func(cmd *cobra.Command, args []string) { }, } // Execute 执行命令 main调用 func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } package cmd import ( "github.com/spf13/cobra" "github.com/spf13/viper" "os" ) var ( moduleName string ) func init() { // 给rootCmd增加新的命令pullCmd rootCmd.AddCommand(pullCmd) pullCmd.Flags().StringVarP(&moduleName, "module", "m", "", "module name") } var pullCmd = &cobra.Command{ Use: "pull gocn data", Short: "pull gocn data", Run: func(cmd *cobra.Command, args []string) { pullHandler() }, } // pullHandler pullCmd 对应的处理函数 func pullHandler() { if moduleName == "" { fmt.Println("module name is required") os.Exit(1) } switch moduleName { case "cool-go": pullCoolGo() default: fmt.Println("module name is invalid") os.Exit(1) } } func pullCoolGo() { // TODO Write Your Code }
结语
以上简单介绍了如何使用cobra、viper、pflag,当然它们的用法还有很多,这里只做个简单的入门。
可见,通过简单的代码就能构建出比较好的命令行程序。

VSole
网络安全专家