# 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 |