目录

golang命令行库Cli的使用

简介

cli是一个用于构建命令行程序的库。我们之前也介绍过一个用于构建命令行程序的库cobra。在功能上来说两者差不多,cobra的优势是提供了一个脚手架,方便开发。cli非常简洁,所有的初始化操作就是创建一个cli.App结构的对象。通过为对象的字段赋值来添加相应的功能。

快速使用

安装cli库,有v1v2两个版本。如果没有特殊需求,一般安装v2版本:

1
go get -u github.com/urfave/cli/v2

使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import (
	"fmt"
	"github.com/urfave/cli/v2"
	"log"
	"os"
)

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)
	}
}

使用非常简单,理论上创建一个cli.App结构的对象,然后调用其Run()方法,传入命令行的参数即可。一个空白的cli应用程序如下:

1
2
3
func main(){
	(&cli.App{}).Run(os.Args)
}

但是这个空白程序没有什么用处。我们的hello world程序,设置了Name/Usage/ActionNameUsage都显示在帮助中,Action是调用该命令行程序时实际执行的函数,需要的信息可以从参数cli.Context获取。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
C:\Users\xx\Desktop\git\mydocker1>go run main.go
hello world

除了这个,cli为我们额外生成了帮助信息
C:\Users\xx\Desktop\git\mydocker1>go run main.go --help
NAME:
   hello - hello world example

USAGE:
    [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

参数

通过cli.Context的相关方法我们可以获取传给命令行的参数信息:

  • NArg():返回参数个数;
  • Args():返回cli.Args对象,调用其Get(i)获取位置i上的参数。

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
	"fmt"
	"github.com/urfave/cli/v2"
	"log"
	"os"
)

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)
	}

}

输出

1
2
3
4
C:\Users\xx\Desktop\git\mydocker1>go run main.go /bin/bash --c sss
1: /bin/bash
2: --c
3: sss

选项

一个好用的命令行程序怎么会少了选项呢?cli设置和获取选项非常简单。在cli.App{}结构初始化时,设置字段Flags即可添加选项。Flags字段是[]cli.Flag类型,cli.Flag实际上是接口类型。cli为常见类型都实现了对应的XxxFlag,如BoolFlag/DurationFlag/StringFlag等。它们有一些共用的字段,Name/Value/Usage(名称/默认值/释义)。看示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func main(){
	app := &cli.App{
		Name: "flags",
		Usage: "flag example",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name: "lang",
				Value: "english",
				Usage: "language for the greeting",
			},
		},
		Action: func(c *cli.Context)error {
			for i:=0;i<c.NArg();i++{
				fmt.Printf("%d: %s\n",i+1,c.Args().Get(i))
			}
			name := "world"
			//获取flag lang的值
			if c.String("lang") == "english"{
				fmt.Println("hello",name)
			}else {
				fmt.Println("你好",name)
			}
			return nil
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
默认调用
C:\Users\xx\Desktop\git\mydocker1>go run main.go
hello world

设置非英语
C:\Users\xx\Desktop\git\mydocker1>go run main.go -lang chinese
你好 world

C:\Users\xx\Desktop\git\mydocker1>go run main.go --lang chinese
你好 world

传入参数作为人名
C:\Users\xx\Desktop\git\mydocker1>go run main.go --lang chinese xx
你好 xx

通过--help查看帮助
C:\Users\xx\Desktop\git\mydocker1>go run main.go --help
NAME:
   flags - flag example

USAGE:
    [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --lang value  language for the greeting (default: "english")
   --help, -h    show help

存入变量

除了通过c.Type(name)来获取选项的值,我们还可以将选项存到某个预先定义好的变量中。只需要设置Destination字段为变量的地址即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func main(){
	var language string
	app := &cli.App{
		Name: "flags",
		Usage: "flag example",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name: "lang",
				Value: "english",
				Usage: "language for the greeting",
				Destination: &language,
			},
		},
		Action: func(c *cli.Context)error {

			name := "world"
			if c.NArg() > 0{
				name = c.Args().Get(0)
			}
			//获取flag lang的值
			if language == "english"{
				fmt.Println("hello",name)
			}else {
				fmt.Println("你好",name)
			}
			return nil
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

占位值

cli可以在Usage字段中为选项设置占位值,占位值通过反引号 ` 包围。只有第一个生效,其他的维持不变。占位值有助于生成易于理解的帮助信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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)
	}

}

设置占位值之后,帮助信息中,该占位值会显示在对应的选项后面,对短选项也是有效的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
C:\Users\xx\Desktop\git\mydocker1>go run main.go --help
NAME:


USAGE:
    [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --config FILE  Load configuration from FILE
   --help, -h     show help

选项别名

选项可以设置多个别名,设置对应选项的Aliases字段即可(如果是v1版本,直接在Name,可以以,分隔设置多个值),例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func main(){
	var language string
	app := &cli.App{
		Name: "flags",
		Usage: "flag example",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name: "lang",
				Aliases: []string{"language","l"},
				Value: "english",
				Usage: "language for the greeting",
				Destination: &language,
			},
		},
		Action: func(c *cli.Context)error {

			name := "world"
			if c.NArg() > 0{
				name = c.Args().Get(0)
			}
			//获取flag lang的值
			if language == "english"{
				fmt.Println("hello",name)
			}else {
				fmt.Println("你好",name)
			}
			return nil
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

查看帮助信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
C:\Users\xx\Desktop\git\mydocker1>go run main.go --help
NAME:
   flags - flag example

USAGE:
    [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --lang value, --language value, -l value  language for the greeting (default: "english")
   --help, -h                                show help


使用--lang chinese、--language chinese和-l chinese效果是一样的。如果通过不同的名称指定同一个选项,会报错:
C:\Users\xx\Desktop\git\mydocker1>go run main.go --lang chinese xx
你好 xx

C:\Users\xx\Desktop\git\mydocker1>go run main.go --language chinese xx
你好 xx

C:\Users\xx\Desktop\git\mydocker1>go run main.go --l chinese xx
你好 xx

C:\Users\xx\Desktop\git\mydocker1>go run main.go --l chinese --lang xx
Cannot use two forms of the same flag: l lang

环境变量

除了通过执行程序时手动指定命令行选项,我们还可以读取指定的环境变量作为选项的值。只需要将环境变量的名字设置到选项对象的EnvVars字段即可。可以指定多个环境变量名字,cli会依次查找,第一个有值的环境变量会被使用。(v1版本只有EnvVar,可以以,分隔设置多个值)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func main(){
	app := &cli.App{
		Name: "flags",
		Usage: "flag example",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name: "lang,language,l",
				Value: "english",
				Usage: "language for the greeting",
				EnvVars: []string{"APP_LANG","SYSTEM_LANG"},
			},
		},
		Action: func(c *cli.Context)error {

			name := "world"
			if c.NArg() > 0{
				name = c.Args().Get(0)
			}
			//获取flag lang的值
			if c.String("lang") == "english"{
				fmt.Println("hello",name)
			}else {
				fmt.Println("你好",name)
			}
			return nil
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
C:\Users\xx\Desktop\git\mydocker1>go run main.go
hello world

C:\Users\xx\Desktop\git\mydocker1>set APP_LANG=chinese

C:\Users\xx\Desktop\git\mydocker1>go run main.go
你好 world

C:\Users\xx\Desktop\git\mydocker1>go run main.go
hello world

C:\Users\xx\Desktop\git\mydocker1>set SYSTEM_LANG=chinese

C:\Users\xx\Desktop\git\mydocker1>go run main.go
你好 world

文件

cli还支持从文件中读取选项的值,设置选项对象的FilePath字段为文件路径:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func main(){
	app := &cli.App{
		Name: "flags",
		Usage: "flag example",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name: "lang,language,l",
				Value: "english",
				Usage: "language for the greeting",
				FilePath: "./lang.txt",
			},
		},
		Action: func(c *cli.Context)error {

			name := "world"
			if c.NArg() > 0{
				name = c.Args().Get(0)
			}
			//获取flag lang的值
			if c.String("lang") == "english"{
				fmt.Println("hello",name)
			}else {
				fmt.Println("你好",name)
			}
			return nil
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

main.go同级目录创建一个lang.txt,输入内容chinese。然后编译运行程序:

1
2
3
C:\Users\xx\Desktop\git\mydocker1>go run main.go
你好 world

cli还支持从YAML/JSON/TOML等配置文件中读取选项值

有一个单独的包altsrc增加了对其他文件格式如(YAML/JSON/TOML)等配置文件获取标志值的支持

目前支持的输入源格式:

  • YAML
  • JSON
  • TOML

但开发人员可以通过实现altsrc来添加对其他输入源的支持。InputSourceContext用于指定源。

YAML示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
	"fmt"
	"github.com/urfave/cli/v2/altsrc"
	"github.com/urfave/cli/v2"
	"log"
	"os"
)

func main(){
	flags := []cli.Flag{
		//yaml多级使用.分割
		altsrc.NewStringFlag(&cli.StringFlag{Name: "test.lang",Aliases: []string{"lang"}}),
		//指定加载的yaml配置文件路径
		&cli.StringFlag{
			Name: "load",
			Usage: "load yaml `config` ",
		},
	}

	app := &cli.App{
		Name: "flags",
		Usage: "flag example",
		Flags: flags,
		//在执行命令前加载配置文件
		Before: altsrc.InitInputSourceWithContext(flags,altsrc.NewYamlSourceFromFlagFunc("load")),
		Action: func(c *cli.Context)error {

			name := "world"
			if c.NArg() > 0{
				name = c.Args().Get(0)
			}
			//获取flag lang的值
			if c.String("test.lang") == "english"{
				fmt.Println("hello",name)
			}else {
				fmt.Println("你好",name)
			}
			return nil
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

创建yaml load.yaml

1
2
3
test:
  lang: english

运行

1
2
3
4
5
6
C:\Users\xx\Desktop\git\mydocker1>go run main.go
你好 world

C:\Users\xx\Desktop\git\mydocker1>go run main.go   --load load.yaml
hello world

JSON示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
	"fmt"
	"github.com/urfave/cli/v2/altsrc"
	"github.com/urfave/cli/v2"
	"log"
	"os"
)

func main(){
	flags := []cli.Flag{
		//json多级使用.分割
		altsrc.NewStringFlag(&cli.StringFlag{Name: "test.lang",Aliases: []string{"lang"}}),
		//指定加载的json配置文件路径
		&cli.StringFlag{
			Name: "load",
			Usage: "load json `config` ",
		},
	}

	app := &cli.App{
		Name: "flags",
		Usage: "flag example",
		Flags: flags,
		//在执行命令前加载配置文件
		Before: altsrc.InitInputSourceWithContext(flags,altsrc.NewJSONSourceFromFlagFunc("load")),
		Action: func(c *cli.Context)error {

			name := "world"
			if c.NArg() > 0{
				name = c.Args().Get(0)
			}
			//获取flag lang的值
			if c.String("test.lang") == "english"{
				fmt.Println("hello",name)
			}else {
				fmt.Println("你好",name)
			}
			return nil
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

创建json文件 load1.json

1
2
3
4
5
{
  "test": {
    "lang": "english"
  }
}

运行

1
2
3
4
5
6
7
8
9
C:\Users\xx\Desktop\git\mydocker1>go run main.go
你好 world

C:\Users\xx\Desktop\git\mydocker1>go run main.go   --load load1.json
hello world

C:\Users\xx\Desktop\git\mydocker1>go run main.go --lang chinese   --load load1.json
你好 world

优先级

标志值源的优先级如下(从高到低):

  • 用户指定的命令行选项值
  • 环境变量(如果指定)
  • 配置文件(如果指定)
  • 默认定义在标志

组合短选项

我们时常会遇到有多个短选项的情况。例如 linux 命令ls -a -l,可以简写为ls -alcli也支持短选项合写,只需要设置cli.AppUseShortOptionHandling字段为true即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
	"fmt"
	"github.com/urfave/cli/v2"
	"log"
	"os"
)

func main(){

	app := &cli.App{
		UseShortOptionHandling: true,
		Commands: []*cli.Command{
			{
				Name: "short",
				Usage: "complete a task on the list",
				Flags: []cli.Flag{
					&cli.BoolFlag{Name: "server",Aliases: []string{"s"}},
					&cli.BoolFlag{Name: "option",Aliases: []string{"o"}},
					&cli.BoolFlag{Name: "message",Aliases: []string{"m"}},
				},
				Action: func(c *cli.Context) error {
					fmt.Println("server",c.Bool("server"))
					fmt.Println("option",c.Bool("option"))
					fmt.Println("message",c.Bool("message"))
					return nil
				},
			},
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

运行

1
2
3
4
5
C:\Users\xx\Desktop\git\mydocker1>go run main.go short  -som ss
server true
option true
message true

需要特别注意一点,设置UseShortOptionHandlingtrue之后,我们不能再通过-指定选项了,这样会产生歧义。例如-langcli不知道应该解释为l/a/n/g 4 个选项还是lang 1 个。--还是有效的。

必要选项

如果将选项的Required字段设置为true,那么该选项就是必要选项。必要选项必须指定,否则会报错:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
	"fmt"
	"github.com/urfave/cli/v2"
	"log"
	"os"
)

func main(){

	app := &cli.App{
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name: "lang",
				Value: "english",
				Usage: "language for the greeting",
				Required: true,
			},
		},
		Action: func(c *cli.Context) error {
			if c.String("lang") == "english"{
				fmt.Println("hello")
			}else {
				fmt.Println("你好")
			}
			return nil
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
不指定lang时
C:\Users\xx\Desktop\git\mydocker1>go run main.go
NAME:
   main.exe - A new cli application

USAGE:
   main.exe [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --lang value  language for the greeting (default: "english")
   --help, -h    show help (default: false)
2021/09/19 13:49:28 Required flag "lang" not set

指定lang
C:\Users\xx\Desktop\git\mydocker1>go run main.go  --lang chinese
你好

帮助文档中的默认值

默认情况下,帮助文本中选项的默认值显示为Value字段值。有些时候,Value并不是实际的默认值。这时,我们可以通过DefaultText设置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
	"fmt"
	"github.com/urfave/cli/v2"
	"log"
	"os"
)

func main(){

	app := &cli.App{
		Flags: []cli.Flag{
			&cli.IntFlag{
				Name: "port",
				Value: 0,
				Usage: "Use a randomized port",
				DefaultText: "random",
			},
		},
		Action: func(c *cli.Context) error {
			if c.String("lang") == "english"{
				fmt.Println("hello")
			}else {
				fmt.Println("你好")
			}
			return nil
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

上面代码逻辑中,如果Value设置为 0 就随机一个端口,这时帮助信息中default: 0就容易产生误解了。通过DefaultText可以避免这种情况:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
C:\Users\xx\Desktop\git\mydocker1>go run main.go  --help
NAME:
   main.exe - A new cli application

USAGE:
   main.exe [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 remote命令下有add/rename/remove等子命令,git submodule下有add/status/init/update等子命令。

cli通过设置cli.App的Commands字段添加命令,设置各个命令的SubCommands字段,即可添加子命令。非常方便!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package main

import (
	"fmt"
	"github.com/urfave/cli/v2"
	"log"
	"os"
)

func main(){

	app := &cli.App{
		Commands: []*cli.Command{
			{
				Name: "add",
				Aliases: []string{"a"},
				Usage: "add a task to the list",
				Action: func(c *cli.Context) error {
					fmt.Println("added task:",c.Args().First())
					return nil
				},
			},
			{
				Name: "complete",
				Aliases: []string{"c"},
				Usage: "complete a task on the list",
				Action: func(c *cli.Context) error {
					fmt.Println("completed task:",c.Args().First())
					return nil
				},
			},
			{
				Name: "template",
				Aliases: []string{"t"},
				Usage: "option for task templates",
				Subcommands: []*cli.Command{
					{
						Name: "add",
						Usage: "add a new template",
						Action: func(c *cli.Context) error {
							fmt.Println("new task template",c.Args().First())
							return nil
						},
					},
					{
						Name: "remove",
						Usage: "remove an existing template",
						Action: func(c *cli.Context) error {
							fmt.Println("remove task:",c.Args().First())
							return nil
						},
					},
				},
			},
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

上面定义了 3 个命令add/complete/templatetemplate命令定义了 2 个子命令add/remove。编译、运行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
C:\Users\xx\Desktop\git\mydocker1>go run main.go  --help
NAME:
   main.exe - A new cli application

USAGE:
   main.exe [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  option for task templates
   help, h      Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h  show help (default: false)

注意一点,子命令默认不显示在帮助信息中,需要显式调用子命令所属命令的帮助(./subcommand template --help):

C:\Users\xx\Desktop\git\mydocker1>go run main.go t --help
NAME:
   main.exe template - option for task templates

USAGE:
   main.exe template command [command options] [arguments...]

COMMANDS:
   add      add a new template
   remove   remove an existing template
   help, h  Shows a list of commands or help for one command

OPTIONS:
   --help, -h  show help (default: false)


C:\Users\xx\Desktop\git\mydocker1>go run main.go add dating
added task: dating

C:\Users\xx\Desktop\git\mydocker1>go run main.go complete dating
completed task: dating

C:\Users\xx\Desktop\git\mydocker1>go run main.go template add dating
new task template dating

C:\Users\xx\Desktop\git\mydocker1>go run main.go template remove dating
remove task: dating

分类

在子命令数量很多的时候,可以设置Category字段为它们分类,在帮助信息中会将相同分类的命令放在一起展示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func main(){

	app := &cli.App{
		Commands: []*cli.Command{
			{
				Name: "noop",
				Usage: "Usage for noop",
			},
			{
				Name: "add",
				Category: "template",
				Usage: "Usage for add",
			},
			{
				Name: "remove",
				Category: "template",
				Usage: "Usage for remove",
			},
		},
	}

	err := app.Run(os.Args)
	if err != nil{
		log.Fatal(err)
	}

}

运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
C:\Users\xx\Desktop\git\mydocker1>go run main.go  --help
NAME:
   main.exe - A new cli application

USAGE:
   main.exe [global options] command [command options] [arguments...]

COMMANDS:
   noop     Usage for 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 (default: false)

自定义帮助信息

在cli中所有的帮助信息文本都可以自定义,整个应用的帮助信息模板通过AppHelpTemplate指定。命令的帮助信息模板通过CommandHelpTemplate设置,子命令的帮助信息模板通过SubcommandHelpTemplate设置。甚至可以通过覆盖cli.HelpPrinter这个函数自己实现帮助信息输出。下面程序在默认的帮助信息后添加个人网站和微信信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
	"fmt"
	"github.com/urfave/cli"
	"os"
)

func main(){

	cli.AppHelpTemplate = fmt.Sprintf(`%s 
WEBSITE: https://www.xieys.club
WECHAT: 赶路人`,cli.AppHelpTemplate)

	(&cli.App{}).Run(os.Args)
}

运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
C:\Users\xx\Desktop\git\mydocker1>go run main.go  --help
NAME:


USAGE:
    [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

WEBSITE: https://www.xieys.club
WECHAT: 赶路人

改写帮助模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
	"github.com/urfave/cli"
	"os"
)

func main(){

	cli.AppHelpTemplate = `NAME:
	{{.Name}} - {{.Usage}}
USAGE:
	{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{ if len .Authors}}
AUTHOR:	
	{{range .Authors}}{{.}}{{end}}{{end}}{{if .Commands}}
COMMANDS:
	{{range .Commands}}{{if not .HideHelp}}  {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS:
	{{range .VisibleFlags}}{{.}}{{end}}{{end}}{{if .Copyright}}
COPYRIGHT:
	{{.Copyright}}{{end}}{{if .Version}}
VERSION:
	{{.Version}}{{end}}
`
	app := &cli.App{
		Name: "help",
		Usage: "a new cli application",
		Authors: []cli.Author{
			{
				Name: "xieys",
				Email: "xieys_1993l@163.com",
			},
		},
	}
	app.Run(os.Args)
}

{{.XXX}}其中XXX对应cli.App{}结构中设置的字段,例如上面Authors

运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
C:\Users\xx\Desktop\git\mydocker1>go run main.go --help
NAME:
  help - a new cli application
USAGE:
          [global options] command [command options] [arguments...]
AUTHOR:
         xieys <xieys_1993l@163.com>
COMMANDS:
    help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
  --help, -h  show help

通过覆盖HelpPrinter,只输出自己定制的信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
	"fmt"
	"github.com/urfave/cli"
	"io"
	"os"
)

func main(){

	cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
		fmt.Println("Simple Help!")
	}
	(&cli.App{}).Run(os.Args)
}

运行

1
2
3
C:\Users\xx\Desktop\git\mydocker1>go run main.go --help
Simple Help!

内置选项

帮助选项

默认情况下,帮助选项为--help/-h。我们可以通过cli.HelpFlag字段设置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
	"github.com/urfave/cli/v2"
	"os"
)

func main(){

	cli.HelpFlag = &cli.BoolFlag{
		Name: "heeeelp",
		Aliases: []string{"haap"},
		Usage: "help",
	}
	(&cli.App{}).Run(os.Args)
}

查看帮助

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
C:\Users\xx\Desktop\git\mydocker1>go run main.go --help
Incorrect Usage. flag: help requested

NAME:
   main.exe - A new cli application

USAGE:
   main.exe [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --heeeelp, --haap  help (default: false)

C:\Users\xx\Desktop\git\mydocker1>go run main.go --haap
NAME:
   main.exe - A new cli application

USAGE:
   main.exe [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --heeeelp, --haap  help (default: false)

版本选项

默认版本选项-v/--version输出应用的版本信息。我们可以通过cli.VersionFlag设置版本选项 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
	"github.com/urfave/cli/v2"
	"os"
)

func main(){

	cli.VersionFlag = &cli.BoolFlag{
		Name: "print-version",
		Aliases: []string{"V"},
		Usage: "print only the version",
	}
	(&cli.App{
		Name: "version",
		Version: "V1.0.0",
	}).Run(os.Args)
}

运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
C:\Users\xx\Desktop\git\mydocker1>go run main.go --print-version
version version V1.0.0

C:\Users\xx\Desktop\git\mydocker1>go run main.go --help
NAME:
   version - A new cli application

USAGE:
   main.exe [global options] command [command options] [arguments...]

VERSION:
   V1.0.0

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h           show help (default: false)
   --print-version, -V  print only the version (default: false)
   
C:\Users\xx\Desktop\git\mydocker1>go run main.go -V
version version V1.0.0
   

还可以通过设置cli.VersionPrinter字段控制版本信息的输出内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
	"fmt"
	"github.com/urfave/cli/v2"
	"os"
)


func main(){
	cli.VersionPrinter = func(c *cli.Context) {
		fmt.Printf("version = %s reversion=1",c.App.Version)
	}
	cli.VersionFlag = &cli.BoolFlag{
		Name: "print-version",
		Aliases: []string{"V"},
		Usage: "print only the version",
	}
	(&cli.App{
		Name: "version",
		Version: "V1.0.0",
	}).Run(os.Args)
}

运行

1
2
3
C:\Users\xx\Desktop\git\mydocker1>go run main.go -V
version = V1.0.0 reversion=1

除此之外,cli还支持bash自动补全功能,参考