# urfave cli
参考资料
urfave cli 官网:https://cli.urfave.org/
urfave cli github: https://github.com/urfave/cli
photoprism (cli 用法参考) : https://github.com/photoprism/photoprism
# 简介
cli 是一个用于构建命令行程序的库。
# 快速开始
安装 cli 库,有 v1 和 v2 两个版本。如果没有特殊需求,一般安装 v2 版本:
$ go get -u github.com/urfave/cli/v2 | 
使用
package main | |
import (  | |
  "fmt" | |
  "log" | |
  "os" | |
  "github.com/urfave/cli/v2" | |
) | |
func main() {  | |
app := &cli.App{  | |
Name: "hello",  | |
Usage: "hello world example",  | |
Action: func(c *cli.Context) error {  | |
fmt.Println("hello world")  | |
return nil  | |
},  | |
  } | |
err := app.Run(os.Args)  | |
if err != nil {  | |
log.Fatal(err)  | |
  } | |
} | 
运行
# 运行 | |
$go run main.go | |
# 编译后运行 | |
$go build -o hello  | |
$./hello  | 
cli 会为我们额外生成了帮助信息:
$ ./hello --help | |
NAME:  | |
hello - hello world example  | |
USAGE:  | |
hello [global options] command [command options] [arguments...]  | |
COMMANDS:  | |
help, h Shows a list of commands or help for one command  | |
GLOBAL OPTIONS:  | |
--help, -h show help (default: false)  | 
# 帮助信息
var version = "v0.0.1 development"  | |
var log = zerolog.New(os.Stderr).With().Timestamp().Logger()  | |
const appName = "AliyunLogAnalysis"  | |
const appAbout = "Twelveeee"  | |
const appDescription = "阿里云日志分析"  | |
const appCopyright = "(c) 2023 Twelveeee @ Twelveeee"  | |
// Metadata contains build specific information. | |
var Metadata = map[string]interface{}{  | |
"Name": appName,  | |
"About": appAbout,  | |
"Description": appDescription,  | |
"Version": version,  | |
} | |
func main() {  | |
defer func() {  | |
if r := recover(); r != nil {  | |
log.Err(errors.New("hello"))  | |
os.Exit(1)  | |
		} | |
}()  | |
app := cli.NewApp()  | |
app.Usage = appAbout  | |
app.Description = appDescription  | |
app.Version = version  | |
app.Copyright = appCopyright  | |
app.EnableBashCompletion = true  | |
app.Flags = config.Flags.Cli()  | |
app.Commands = command.Commands  | |
app.Metadata = Metadata  | |
	// app.UseShortOptionHandling = true | |
zerolog.SetGlobalLevel(-1)  | |
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix  | |
log.Info().Msg("start app")  | |
if err := app.Run(os.Args); err != nil {  | |
log.Err(err).Msg("run error")  | |
	} | |
} | 
$go run main.go | |
NAME:  | |
main - Twelveeee  | |
USAGE:  | |
main [global options] command [command options] [arguments...]  | |
VERSION:  | |
v0.0.1 development  | |
DESCRIPTION:  | |
阿里云日志分析  | |
COMMANDS:  | |
start, up Starts the server  | |
startDay, upd Starts the server with start day and end day  | |
help, h Shows a list of commands or help for one command  | |
GLOBAL OPTIONS:  | |
--help, -h show help  | |
   --version, -v  print the version | |
COPYRIGHT:  | |
(c) 2023 Twelveeee @ Twelveeee  | 
# 参数
通过 cli.Context 的相关方法我们可以获取传给命令行的参数信息:
NArg():返回参数个数;Args():返回cli.Args对象,调用其Get(i)获取位置i上的参数。
参数是指
$go run main.go fff --lang spanish  | |
# 其中 fff --lang spanish 均为参数 都可以通过 Args ().Get (i) 进行获取 | 
示例:
func main() {  | |
app := &cli.App{  | |
Name: "arguments",  | |
Usage: "arguments example",  | |
Action: func(c *cli.Context) error {  | |
for i := 0; i < c.NArg(); i++ {  | |
fmt.Printf("%d: %s\n", i+1, c.Args().Get(i))  | |
      } | |
return nil  | |
},  | |
  } | |
err := app.Run(os.Args)  | |
if err != nil {  | |
log.Fatal(err)  | |
  } | |
} | 
$go run main.go fff --lang spanish  | |
1: fff | |
2: --lang  | |
3: spanish | 
# 选项
Flags 字段是 []cli.Flag 类型, cli.Flag 实际上是接口类型。 cli 为常见类型都实现了对应的 XxxFlag ,如 BoolFlag/DurationFlag/StringFlag 等。它们有一些共用的字段, Name/Value/Usage (名称 / 默认值 / 释义)。
示例:
func main() {  | |
app := &cli.App{  | |
Flags: []cli.Flag{  | |
&cli.StringFlag{  | |
Name: "lang",  | |
Value: "english",  | |
Usage: "language for the greeting",  | |
},  | |
},  | |
Action: func(cCtx *cli.Context) error {  | |
name := "Nefertiti"  | |
log.Info().Msg(strconv.Itoa(cCtx.NArg()))  | |
if cCtx.NArg() > 0 {  | |
name = cCtx.Args().Get(0)  | |
			} | |
if cCtx.String("lang") == "spanish" {  | |
fmt.Println("Hola", name)  | |
} else {  | |
fmt.Println("Hello", name)  | |
			} | |
return nil  | |
},  | |
	} | |
if err := app.Run(os.Args); err != nil {  | |
log.Err(err).Msg("run error")  | |
	} | |
} | 
# 参数在前 flag 在后,没有识别 flag | |
$go run main.go fff --lang spanish  | |
{"level":"info","time":"2023-11-12T06:31:34Z","message":"3"}  | |
Hello fff  | |
#flag 在前,,参数在后 正常识别 | |
$go run main.go --lang spanish fff  | |
{"level":"info","time":"2023-11-12T06:32:20Z","message":"1"}  | |
Hola fff  | 
# 获取选项的值
获取 flag 可以通过 ctx.Type(name) 也可以绑定 Destination 字段
func main() {  | |
var language string  | |
app := &cli.App{  | |
Flags: []cli.Flag{  | |
&cli.StringFlag{  | |
Name: "lang",  | |
Value: "english",  | |
Usage: "language for the greeting",  | |
          // 这的 Destination | |
Destination: &language,  | |
},  | |
},  | |
Action: func(c *cli.Context) error {  | |
name := "world"  | |
if c.NArg() > 0 {  | |
name = c.Args().Get(0)  | |
      } | |
if language == "english" {  | |
fmt.Println("hello", name)  | |
} else {  | |
fmt.Println("你好", name)  | |
      } | |
return nil  | |
},  | |
  } | |
err := app.Run(os.Args)  | |
if err != nil {  | |
log.Fatal(err)  | |
  } | |
} | 
# 占位符
可以在 Usage 字段中为选项设置占位值,占位值通过反引号 ` 包围。只有第一个生效,其他的维持不变。占位值有助于生成易于理解的帮助信息:
func main() {  | |
app := & cli.App{  | |
Flags : []cli.Flag {  | |
&cli.StringFlag{  | |
Name:"config",  | |
Usage: "Load configuration from `FILE`",  | |
},  | |
},  | |
  } | |
err := app.Run(os.Args)  | |
if err != nil {  | |
log.Fatal(err)  | |
  } | |
} | 
# GLOBAL OPTIONS 里 value 变成了 FILE | |
$go run main.go --help  | |
NAME:  | |
placeholder - A new cli application  | |
USAGE:  | |
placeholder [global options] command [command options] [arguments...]  | |
COMMANDS:  | |
help, h Shows a list of commands or help for one command  | |
GLOBAL OPTIONS:  | |
   --config    Load configuration from FILE | |
--help, -h show help (default: false)  | 
# 别名
flag 可以设置多个别名,设置对应选项的 Aliases 字段即可:
func main() {  | |
app := &cli.App{  | |
Flags: []cli.Flag{  | |
&cli.StringFlag{  | |
Name: "lang",  | |
Aliases: []string{"l"},  | |
Value: "english",  | |
Usage: "language for the greeting",  | |
},  | |
},  | |
    } | |
if err := app.Run(os.Args); err != nil {  | |
log.Fatal(err)  | |
    } | |
} | 
# GLOBAL OPTIONS 里显示,可以使用 --lang 和 - l 去设置 flag | |
# 通过两个效果一样的名称指定同一个选项会报错 | |
$go run main.go -h  | |
NAME:  | |
main - A new cli application  | |
USAGE:  | |
main [global options] command [command options] [arguments...]  | |
COMMANDS:  | |
help, h Shows a list of commands or help for one command  | |
GLOBAL OPTIONS:  | |
--lang value, -l value language for the greeting (default: "english")  | |
--help, -h show help  | 
# 环境变量
读取指定的环境变量作为选项的值。只需要将环境变量的名字设置到选项对象的 EnvVars 字段即可。可以指定多个环境变量名字, cli 会依次查找,第一个有值的环境变量会被使用。
func main() {  | |
app := &cli.App{  | |
Flags: []cli.Flag{  | |
&cli.StringFlag{  | |
Name: "lang",  | |
Aliases: []string{"l"},  | |
Value: "english",  | |
Usage: "language for the greeting",  | |
EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"},  | |
},  | |
},  | |
    } | |
if err := app.Run(os.Args); err != nil {  | |
log.Fatal(err)  | |
    } | |
} | 
# 文件变量
可以从文件中获取变量,从文件中获取变量的优先级大于环境变量
func main() {  | |
app := &cli.App{  | |
Flags: []cli.Flag{  | |
&cli.StringFlag{  | |
Name: "password",  | |
Aliases: []string{"p"},  | |
Usage: "password for the mysql database",  | |
FilePath: "/etc/mysql/password",  | |
},  | |
},  | |
    } | |
if err := app.Run(os.Args); err != nil {  | |
log.Fatal(err)  | |
    } | |
} | 
支持从 YAML、JSON、TOML 中获取变量
官网:https://cli.urfave.org/v2/examples/flags/#values-from-alternate-input-sources-yaml-toml-and-others
参考资料:https://github.com/urfave/cli/issues/1250
这块查阅资料后发现有一系列的坑,官网以及国内的各个博客都没有做出很好的解释
test2: TTAA  | |
test: 1234  | |
key:  | |
child-key: value  | |
child-key2: value2  | 
func main() {  | |
flags := []cli.Flag{  | |
altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}),  | |
altsrc.NewStringFlag(&cli.StringFlag{Name: "test2"}),  | |
altsrc.NewStringFlag(&cli.StringFlag{Name: "child-key2", Aliases: []string{"key.child-key2"}}),  | |
&cli.StringFlag{Name: "load", Value: "yaml.yaml"},  | |
	} | |
app := &cli.App{  | |
Action: func(ctx *cli.Context) error {  | |
fmt.Println("--test value.*default: 0")  | |
fmt.Println("test2:", ctx.String("test2"))  | |
fmt.Println("test:", ctx.Int("test"))  | |
fmt.Println("child-key2:", ctx.String("key.child-key2"))  | |
fmt.Println("child-key2:", ctx.String("child-key2"))  | |
return nil  | |
},  | |
Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")),  | |
Flags: flags,  | |
	} | |
err := app.Run(os.Args)  | |
if err != nil {  | |
log.Err(err).Msg("run error")  | |
	} | |
} | 
$ go run main.go  | |
--test value.*default: 0  | |
test2: TTAA  | |
test: 1234 | |
child-key2: value2  | |
child-key2: value2  | 
如果要读取 yaml 里的变量,需要定义一个 altsrc.NewStringFlag 生成的 flag,里面包含一个 FlagSet
如果 yaml 变量是层级关系,则可以使用点进行间隔,如 key.child-key2 , 也可以通过设置别名,在 Action 中进行获取。
Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")),  | 
这个函数的主要目的是 在程序开始前,初始化 flags 参数
NewYamlSourceFromFlagFunc 中的 load 其实也是 flag 的值,支持通过命令行进行初始化。demo 中使用的是默认的值
# 必要选项
flag 里加 Required
func main() {  | |
app := &cli.App{  | |
Flags: []cli.Flag{  | |
&cli.StringFlag{  | |
Name: "lang",  | |
Value: "english",  | |
Usage: "language for the greeting",  | |
Required: true,  | |
},  | |
},  | |
Action: func(cCtx *cli.Context) error {  | |
output := "Hello"  | |
if cCtx.String("lang") == "spanish" {  | |
output = "Hola"  | |
            } | |
fmt.Println(output)  | |
return nil  | |
},  | |
    } | |
if err := app.Run(os.Args); err != nil {  | |
log.Fatal(err)  | |
    } | |
} | 
# 帮助文本中的默认值
默认情况下,帮助文本中选项的默认值显示为 Value 字段值。有些时候, Value 并不是实际的默认值。这时,我们可以通过 DefaultText 设置:
func main() {  | |
app := &cli.App{  | |
Flags: []cli.Flag{  | |
&cli.IntFlag{  | |
Name: "port",  | |
Value: 0,  | |
Usage: "Use a randomized port",  | |
DefaultText :"random",  | |
},  | |
},  | |
  } | |
err := app.Run(os.Args)  | |
if err != nil {  | |
log.Fatal(err)  | |
  } | |
} | 
上面代码逻辑中,如果 Value 设置为 0 就随机一个端口,这时帮助信息中 default: 0 就容易产生误解了。通过 DefaultText 可以避免这种情况:
$go run main.go --help  | |
NAME:  | |
default-text - A new cli application  | |
USAGE:  | |
default-text [global options] command [command options] [arguments...]  | |
COMMANDS:  | |
help, h Shows a list of commands or help for one command  | |
GLOBAL OPTIONS:  | |
--port value Use a randomized port (default: random)  | |
--help, -h show help (default: false)  | 
# 子命令
类似于 git ,git 里就有大量的命令,很多以某个命令下的子命令存在。例如 git remote 命令下有 add/rename/remove 等子命令, git submodule 下有 add/status/init/update 等子命令。
func main() {  | |
app := &cli.App{  | |
Commands: []*cli.Command{  | |
            { | |
Name: "add",  | |
Aliases: []string{"a"},  | |
Usage: "add a task to the list",  | |
Action: func(cCtx *cli.Context) error {  | |
fmt.Println("added task: ", cCtx.Args().First())  | |
return nil  | |
},  | |
},  | |
            { | |
Name: "complete",  | |
Aliases: []string{"c"},  | |
Usage: "complete a task on the list",  | |
Action: func(cCtx *cli.Context) error {  | |
fmt.Println("completed task: ", cCtx.Args().First())  | |
return nil  | |
},  | |
},  | |
            { | |
Name: "template",  | |
Aliases: []string{"t"},  | |
Usage: "options for task templates",  | |
Subcommands: []*cli.Command{  | |
                    { | |
Name: "add",  | |
Usage: "add a new template",  | |
Action: func(cCtx *cli.Context) error {  | |
fmt.Println("new task template: ", cCtx.Args().First())  | |
return nil  | |
},  | |
},  | |
                    { | |
Name: "remove",  | |
Usage: "remove an existing template",  | |
Action: func(cCtx *cli.Context) error {  | |
fmt.Println("removed task template: ", cCtx.Args().First())  | |
return nil  | |
},  | |
},  | |
},  | |
},  | |
},  | |
    } | |
if err := app.Run(os.Args); err != nil {  | |
log.Fatal(err)  | |
    } | |
} | 
$go run main.go -h  | |
NAME:  | |
main - A new cli application  | |
USAGE:  | |
main [global options] command [command options] [arguments...]  | |
COMMANDS:  | |
   add, a       add a task to the list | |
complete, c complete a task on the list  | |
   template, t  options for task templates | |
help, h Shows a list of commands or help for one command  | |
GLOBAL OPTIONS:  | |
--help, -h show help  | 
子命令默认不显示在帮助信息中,需要显式调用子命令所属命令的帮助
$ go run main.go add -h  | |
NAME:  | |
main add - add a task to the list  | |
USAGE:  | |
main add [command options] [arguments...]  | |
OPTIONS:  | |
--help, -h show help  | 
# 分类
在子命令数量很多的时候,可以设置 Category 字段为它们分类,在帮助信息中会将相同分类的命令放在一起展示:
func main() {  | |
app := &cli.App{  | |
Commands: []*cli.Command{  | |
			{ | |
Name: "noop",  | |
},  | |
			{ | |
Name: "add",  | |
Category: "template",  | |
Usage: "Usage for add",  | |
},  | |
			{ | |
Name: "remove",  | |
Category: "template",  | |
Usage: "Usage for remove",  | |
},  | |
},  | |
	} | |
if err := app.Run(os.Args); err != nil {  | |
log.Fatal(err)  | |
	} | |
} | 
$ go run main.go -h | |
NAME:  | |
main - A new cli application  | |
USAGE:  | |
main [global options] command [command options] [arguments...]  | |
COMMANDS:  | |
noop  | |
help, h Shows a list of commands or help for one command  | |
template:  | |
add Usage for add  | |
     remove  Usage for remove | |
GLOBAL OPTIONS:  | |
--help, -h show help  | 
# 自定义信息
自定义 help 信息 包括版本 appName 等等
var version = "v0.0.1 development"  | |
const appName = "AliyunLogAnalysis"  | |
const appAbout = "Twelveeee"  | |
const appDescription = "日志分析"  | |
const appCopyright = "(c) 2023 Twelveeee @ Twelveeee"  | |
// Metadata contains build specific information. | |
var Metadata = map[string]interface{}{  | |
"Name": appName,  | |
"About": appAbout,  | |
"Description": appDescription,  | |
"Version": version,  | |
} | |
func main() {  | |
defer func() {  | |
if r := recover(); r != nil {  | |
os.Exit(1)  | |
		} | |
}()  | |
app := cli.NewApp()  | |
app.Usage = appAbout  | |
app.Description = appDescription  | |
app.Version = version  | |
app.Copyright = appCopyright  | |
app.EnableBashCompletion = true  | |
app.Flags = config.Flags.Cli()  | |
app.Commands = command.Commands  | |
app.Metadata = Metadata  | |
	// app.UseShortOptionHandling = true | |
log.Info().Msg("start app")  | |
if err := app.Run(os.Args); err != nil {  | |
log.Err(err).Msg("run error")  | |
	} | |
} | 
$ go run main.go  | |
NAME:  | |
main - Twelveeee  | |
USAGE:  | |
main [global options] command [command options] [arguments...]  | |
VERSION:  | |
v0.0.1 development  | |
DESCRIPTION:  | |
日志分析  | |
COMMANDS:  | |
start, up Starts the server  | |
startDay, upd Starts the server with start day and end day  | |
help, h Shows a list of commands or help for one command  | |
GLOBAL OPTIONS:  | |
--help, -h show help  | |
   --version, -v  print the version | |
COPYRIGHT:  | |
(c) 2023 Twelveeee @ Twelveeee  |