Go语言领域,命令行程序占据比较重要的位置。因此,命令行程序的快速开发及工具选择就成为一个不可忽视的问题。本文简要总结使用Go语言开发命令行程序中常用的几个框架。
一、Cobra
Cobra 是关于 golang 的一个命令行解析库,用它能够快速创建功能强大的 cli 应用程序和命令行工具。
cobra既是一个用于创建强大现代CLI应用程序的库,也是一个生成应用程序和命令文件的程序。cobra被用在很多go语言的项目中,比如 Kubernetes、Docker、Istio、ETCD、Hugo、Github CLI等等。
hugo:号称速度最快的静态站点生成器。
我们平常用到命令:git commit -m “message”,docker containter start 等都可以用 cobra 来实现。
- Cobra 官网:https://cobra.dev/
- github地址:https://github.com/spf13/cobra
功能特性介绍
- 很多子命令的CLIS: 比如 app server、app fetch 等
- 支持嵌套子命令(sub-command)
- 轻松完成应用程序和命令的创建:cobra init appname 和 cobra add cmdname
- 为应用程序生成 man 手册
- 全局、本地和级联 flag
- 为 shell 程序完成自动提示(bash,zsh,fish, powershell etc.)
- 支持命令行别名,可以帮助你更容易更改内容而不破坏他们
- 灵活定义自己的help、usage信息
- 可选集成 viper 配置管理工具库
Cobra命令结构说明
Cobra 命令结构由3部分组成:
commands、arguments 和 flags
commands:
命令行,代表行为动作,要执行的一个动作。每个命令还可以包含子命令,分为:rootCmd 和 subCmd。程序中具体对象是 cobra.Command{},这个是根命令;子命令(subCmd)用 rootCmd.AddCommand() 添加,子命令通常也会单独存一个文件,并通过一个全局变量让 rootCmd 可以 add 它。
arguments:
命令行参数,通常是 []string 表示。
flags:
命令行选项。对 command 进一步的控制。通常用一短横 - 或者两短横 -- 标识,程序中读取存储在变量中。
cobra 命令行格式:
APPNAME VERB NOUN --ADJECTIVE
APPNEM COMMAND ARG --FLAG
例子说明:
# server代表command,port代表flag
hugo server --port=1313
# clone代表command,URL代表argument,bare代表flag
git clone URL --bare
二、go语言内置flag库
Flag库是golang自带的命令行参数库。可以通过 -name abc的方式 或者-name=abc 的方式来给命令行参数name传入 abc这个值。
1. 简单使用命令行参数的情况
- 需要特别注意 一定不要忘记调用 flag.Parse() 否则你的参数都是默认值。
- 另外需要注意StringVar 中第一个参数一定是变量的指针 这样才能将解析到的值添加到变量中。
package main
import (
"flag"
"fmt"
)
// 适用于最简单的 只有一组命令行参数的情况
// 带有等号的格式
// eg: go run main.go -name=ikun -id=29238 -male
// 不带有等号的格式
// eg: go run main.go -name ikun -id 29238 -male
func main() {
var name string
var studentId int
var isMale bool
// 定义 flag.xxxVar(&变量, 参数名, 默认值, 帮助信息)
flag.StringVar(&name, "name", "caixukun.666", "姓名")
flag.IntVar(&studentId, "id", 128, "学号")
flag.BoolVar(&isMale, "male", false, "是男性")
// 注意一定要有解析 否则变量都是默认值
flag.Parse()
// 查看解析结果
fmt.Printf("Name=%s, StudentId=%d, IsMale=%t\n", name, studentId, isMale)
}
2. 有子命令的情况
类似于docker container -ls 中container 就是一个子命令
1. 这种情况下需要首先解析一次。否则flag.Args是无效的。
2. 使用flag.Args()[0] 来判断使用的是哪个子命令
3. 然后重新创建一个命名的flagSet 进一步解析 flag.Args[1:] 部分的命令行参数作为子命令的命令行参数
package main
import (
"flag"
"fmt"
)
// 适用于有子命令的情况
// 不带有等号的格式
// eg: student子命令: go run main.go student -name ikun -id 29238 -male
// eg: student子命令: go run main.go teacher -name mayun -salary 20000
// 带有等号的格式
// eg: student子命令: go run main.go student -name=ikun -id=29238 -male
// eg: student子命令: go run main.go teacher -name=mayun -salary=20000
func main() {
//首先要用全局默认FlagSet解析一遍 否则flag.Args是无效的。
flag.Parse()
flagArgs := flag.Args()
switch flagArgs[0] {
case "student":// student子命令解析
var name string
var studentId int
var isMale bool
stuendtFlags := flag.NewFlagSet("student", flag.ExitOnError)
stuendtFlags.StringVar(&name, "name", "caixukun.666", "姓名")
stuendtFlags.IntVar(&studentId, "id", 128, "学号")
stuendtFlags.BoolVar(&isMale, "male", false, "是否男性")
// 索引从1开始 索引0是子命令 "student" 字符串
err := stuendtFlags.Parse(flagArgs[1:])
if err != nil {
fmt.Printf("Student Invalid cmd option\n")
return
}
fmt.Printf("Student: Name=%s, StudentId=%d, IsMale=%t\n", name, studentId, isMale)
case "teacher":
var name string
var salary int64
teacherFlags := flag.NewFlagSet("teacher", flag.ExitOnError)
teacherFlags.StringVar(&name, "name", "caixukun.666", "姓名")
teacherFlags.Int64Var(&salary, "salary", 128, "薪资")
err := teacherFlags.Parse(flagArgs[1:])
if err != nil {
fmt.Printf("Teacher Invalid cmd option\n")
return
}
fmt.Printf("Teacher: Name=%s, Salary=%d\n", name, salary)
default:
fmt.Printf("Invalid cmd option\n")
}
}
三、mow.cli
地址:https://github.com/jawher/mow.cli
著名的基于Go语言的爬虫框架colly就是基于此命令行框架。
简单举例,更复杂的见官网:
package main
import (
"fmt"
"os"
"github.com/jawher/mow.cli"
)
func main() {
// create an app
app := cli.App("cp", "Copy files around")
// Here's what differentiates mow.cli from other CLI libraries:
// This line is not just for help message generation.
// It also validates the call to reject calls with less than 2 arguments
// and split the arguments between SRC or DST
app.Spec = "[-r] SRC... DST"
var (
// declare the -r flag as a boolean flag
recursive = app.BoolOpt("r recursive", false, "Copy files recursively")
// declare the SRC argument as a multi-string argument
src = app.StringsArg("SRC", nil, "Source files to copy")
// declare the DST argument as a single string (string slice) arguments
dst = app.StringArg("DST", "", "Destination where to copy files to")
)
// Specify the action to execute when the app is invoked correctly
app.Action = func() {
fmt.Printf("Copying %v to %s [recursively: %v]\n", *src, *dst, *recursive)
}
// Invoke the app passing in os.Args
app.Run(os.Args)
}
参考
- https://zhuanlan.zhihu.com/p/597566246
- https://blog.csdn.net/qq_30614345/article/details/130977888
- https://github.com/jawher/mow.cli