golang语言cli库/Golang的CLI 命令行程序开发

很多用Go写的命令行程序都用了urfave/cli这个库,包括geth,有必要简单了解一下。

用C写过命令行程序的人应该都不陌生,我们需要根据argc/argv一个个地解析命令行参数,调用不同的函数,最后还要写一个usage()函数用于打印帮助信息。urfave/cli把这个过程做了一下封装,抽象出flag/command/subcommand这些模块,用户只需要提供一些模块的配置,参数的解析和关联在库内部完成,帮助信息也可以自动生成。

举个例子,我们想要实现下面这个命令行程序:

NAME:
   GoTest - hello world

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

VERSION:
   1.2.3

COMMANDS:
     help, h  Shows a list of commands or help for one command
   arithmetic:
     add, a  calc 1+1
     sub, s  calc 5-3
   database:
     db  database operations

GLOBAL OPTIONS:
   --lang FILE, -l FILE    read from FILE (default: "english")
   --port value, -p value  listening port (default: 8000)
   --help, -h              Help!Help!
   --print-version, -v     print version

1. 基本结构

导入包以后,通过cli.NewApp()创建一个实例,然后调用Run()方法就实现了一个最基本的命令行程序了。

当然,为了让我们的程序干点事情,可以指定一下入口函数app.Action,具体写法如下:

 import (
        "fmt"
	"gopkg.in/urfave/cli.v1"
)

func main() {
	app := cli.NewApp()
	app.Action = func(c *cli.Context) error {
		fmt.Println("BOOM!")
		return nil
	}

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

2. 公共配置

就是帮助里需要显示的一些基本信息:

	app.Name = "GoTest"
	app.Usage = "hello world"
	app.Version = "1.2.3"

3. Flag配置

具体对应于帮助中的以下信息:

   --lang FILE, -l FILE    read from FILE (default: "english")
   --port value, -p value  listening port (default: 8000)

对应代码:

	var language string

	app.Flags = []cli.Flag {
		cli.IntFlag {
			Name: "port, p",
			Value: 8000,
			Usage: "listening port",
		},
		cli.StringFlag {
			Name: "lang, l",
			Value: "english",
			Usage: "read from `FILE`",
			Destination: &language,
		},
	}

可以看到,每一个flag都对应一个cli.Flag接口的实例。

Name字段中逗号后面的字符表示flag的简写,也就是说"--port"和"-p"是等价的。

Value字段可以指定flag的默认值。

Usage字段是flag的描述信息。

Destination字段可以为该flag指定一个接收者,比如上面的language变量。解析完"--lang"这个flag后会自动存储到这个变量里,后面的代码就可以直接使用这个变量的值了。

另外,如果你想给用户增加一些属性值类型的提示,可以通过占位符(placeholder)来实现,比如上面的"--lang FILE"。占位符通过``符号来标识。

我们可以在app.Action中测试一下打印这些flag的值:

	app.Action = func(c *cli.Context) error {
		fmt.Println("BOOM!")
		fmt.Println(c.String("lang"), c.Int("port"))
		fmt.Println(language)
		return nil
	}

另外,正常来说帮助信息里的flag是按照代码里的声明顺序排列的,如果你想让它们按照字典序排列的话,可以借助于sort:

import "sort"
sort.Sort(cli.FlagsByName(app.Flags))

最后,help和version这两个flag有默认实现,也可以自己改:

	cli.HelpFlag = cli.BoolFlag {
		Name: "help, h",
		Usage: "Help!Help!",
	}
	
	cli.VersionFlag = cli.BoolFlag {
		Name: "print-version, v",
		Usage: "print version",
	}

4. Command配置

命令行程序除了有flag,还有command(比如git log, git commit等等)。

另外每个command可能还有subcommand,也就必须要通过添加两个命令行参数才能完成相应的操作。比如我们的db命令包含2个子命令,如果输入GoTest db -h会显示下面的信息:

NAME:
   GoTest db - database operations

USAGE:
   GoTest db command [command options] [arguments...]

COMMANDS:
     insert  insert data
     delete  delete data

OPTIONS:
   --help, -h  Help!Help!

每个command都对应于一个cli.Command接口的实例,入口函数通过Action指定。如果你想像在帮助信息里实现分组显示,可以为每个command指定一个Category。具体代码如下:

	app.Commands = []cli.Command {
		{
			Name: "add",
			Aliases: []string{"a"},
			Usage: "calc 1+1",
			Category: "arithmetic",
			Action: func(c *cli.Context) error {
				fmt.Println("1 + 1 = ", 1 + 1)
				return nil
			},
		},
		{
			Name: "sub",
			Aliases: []string{"s"},
			Usage: "calc 5-3",
			Category: "arithmetic",
			Action: func(c *cli.Context) error {
				fmt.Println("5 - 3 = ", 5 - 3)
				return nil
			},
		},
		{
			Name: "db",
			Usage: "database operations",
			Category: "database",
			Subcommands: []cli.Command {
				{
					Name: "insert",
					Usage: "insert data",
					Action: func(c *cli.Context) error {
						fmt.Println("insert subcommand")
						return nil
					},
				},
				{
					Name: "delete",
					Usage: "delete data",
					Action: func(c *cli.Context) error {
						fmt.Println("delete subcommand")
						return nil
					},
				},
			},
		},
	}

如果你想在command执行前后执行后完成一些操作,可以指定app.Before/app.After这两个字段:

	app.Before = func(c *cli.Context) error {
		fmt.Println("app Before")
		return nil
	}
	app.After = func(c *cli.Context) error {
		fmt.Println("app After")
		return nil
	}

具体测试一下:

$ GoTest add
$ GoTest db insert

5. 小结

总体来说,urfave/cli这个库还是很好用的,完成了很多routine的工作,程序员只需要专注于具体业务逻辑的实现。

附完整demo代码:

package cli

import (
	"fmt"
	"os"
	"log"
	"sort"
	"gopkg.in/urfave/cli.v1"
)

func Run() {
	var language string

	app := cli.NewApp()
	app.Name = "GoTest"
	app.Usage = "hello world"
	app.Version = "1.2.3"
	app.Flags = []cli.Flag {
		cli.IntFlag {
			Name: "port, p",
			Value: 8000,
			Usage: "listening port",
		},
		cli.StringFlag {
			Name: "lang, l",
			Value: "english",
			Usage: "read from `FILE`",
			Destination: &language,
		},
	}
	app.Commands = []cli.Command {
		{
			Name: "add",
			Aliases: []string{"a"},
			Usage: "calc 1+1",
			Category: "arithmetic",
			Action: func(c *cli.Context) error {
				fmt.Println("1 + 1 = ", 1 + 1)
				return nil
			},
		},
		{
			Name: "sub",
			Aliases: []string{"s"},
			Usage: "calc 5-3",
			Category: "arithmetic",
			Action: func(c *cli.Context) error {
				fmt.Println("5 - 3 = ", 5 - 3)
				return nil
			},
		},
		{
			Name: "db",
			Usage: "database operations",
			Category: "database",
			Subcommands: []cli.Command {
				{
					Name: "insert",
					Usage: "insert data",
					Action: func(c *cli.Context) error {
						fmt.Println("insert subcommand")
						return nil
					},
				},
				{
					Name: "delete",
					Usage: "delete data",
					Action: func(c *cli.Context) error {
						fmt.Println("delete subcommand")
						return nil
					},
				},
			},
		},
	}
	app.Action = func(c *cli.Context) error {
		fmt.Println("BOOM!")
		fmt.Println(c.String("lang"), c.Int("port"))
		fmt.Println(language)

		// if c.Int("port") == 8000 {
		// 	return cli.NewExitError("invalid port", 88)
		// }

		return nil
	}
	app.Before = func(c *cli.Context) error {
		fmt.Println("app Before")
		return nil
	}
	app.After = func(c *cli.Context) error {
		fmt.Println("app After")
		return nil
	}

	sort.Sort(cli.FlagsByName(app.Flags))

	cli.HelpFlag = cli.BoolFlag {
		Name: "help, h",
		Usage: "Help!Help!",
	}
	
	cli.VersionFlag = cli.BoolFlag {
		Name: "print-version, v",
		Usage: "print version",
	}

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

更多文章欢迎关注“鑫鑫点灯”专栏:https://blog.csdn.net/turkeycock/article/category/7669858

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果你在 Golang 中连接 Redis 时失败,可能是以下几个原因: 1. Redis 服务器未启动 请确保 Redis 服务器已经在运。你可以在命令下输入 `redis-cli` 命令,查看 Redis 是否能够正常启动。 2. Redis 服务器未开启远程访问 默认情况下,Redis 只允许本地访问。如果你需要从远程连接 Redis,需要修改 Redis 的配置文件,将 `bind` 属性设置为 Redis 服务器的 IP 地址。例如,如果 Redis 服务器的 IP 地址为 192.168.1.100,你需要将配置文件中的 `bind` 属性设置为 `bind 192.168.1.100`。 3. Redis 服务器的防火墙未开放端口 如果 Redis 服务器的防火墙开启了,你需要开放 Redis 服务器的端口号。默认情况下,Redis 使用的端口号为 6379。你可以在 Redis 服务器的防火墙中开放该端口。 4. Golang 代码中的连接参数错误 请检查你的 Golang 代码中的连接参数是否正确。例如,你需要确保 Redis 服务器的 IP 地址、端口号、密码等信息都正确。 以下是一个简单的 Golang 代码连接 Redis 的示例: ```go package main import ( "github.com/go-redis/redis" ) func main() { client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set DB: 0, // use default DB }) pong, err := client.Ping().Result() if err != nil { fmt.Println("Failed to ping Redis:", err) return } fmt.Println("Successfully connected to Redis:", pong) } ``` 你可以根据自己的实际情况修改该代码中的连接参数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值