Go 学习笔记(10):cli 命令行的使用
Go 语言的程序编译之后是一个可执行的二进制文件,这个特性使得 Go 非常适合做命令行工具,因为一套代码只需要在不同系统上面编译,就可以在所有系统上直接运行,无需运行环境的安装。这篇博文记录 Go 比较流行的命令行工具的使用心得。
cli 库的使用
源码地址:https://github.com/urfave/cli
一个多命令的案例
这个示例的工具名为 mycli
,它支持两个命令:greet
和 calculate
。
首先,需要安装 github.com/urfave/cli/v2
这个库:
go get github.com/urfave/cli/v2
接下来,创建一个名为 main.go
的文件,内容如下:
package main
import (
"fmt"
"os"
"strconv"
"github.com/urfave/cli/v2"
)
func main() {
// 创建一个新的 CLI 应用
app := cli.NewApp()
// 设置应用的信息
app.Name = "mycli"
app.Usage = "A simple CLI tool"
app.Version = "1.0.0"
// 定义 greet 命令
greetCommand := &cli.Command{
Name: "greet",
Aliases: []string{"g"},
Usage: "Greet someone",
Action: func(c *cli.Context) error {
name := c.Args().First()
if name == "" {
name = "World"
}
fmt.Println("Hello,", name)
return nil
},
}
// 定义 calculate 命令
calculateCommand := &cli.Command{
Name: "calculate",
Aliases: []string{"c"},
Usage: "Perform calculation",
Flags: []cli.Flag{
&cli.IntFlag{
Name: "num1",
Usage: "First number",
},
&cli.IntFlag{
Name: "num2",
Usage: "Second number",
},
},
Action: func(c *cli.Context) error {
num1 := c.Int("num1")
num2 := c.Int("num2")
result := num1 + num2
fmt.Printf("%d + %d = %d\n", num1, num2, result)
return nil
},
}
// 将命令添加到应用中
app.Commands = []*cli.Command{greetCommand, calculateCommand}
// 运行应用
err := app.Run(os.Args)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
这个示例中,首先创建了一个新的 CLI 应用,设置了应用的名称、用途和版本信息。
然后,定义了两个命令:greet
和 calculate
。每个命令都有一个 Name
、Aliases
、Usage
和 Action
。Action
字段指定了命令被执行时的操作。
对于 greet
命令,这里简单地获取第一个参数作为名字,并输出 "Hello, {name}"。如果没有提供参数,则默认使用 "World"。
对于 calculate
命令,这里定义了两个整数类型的标志(flags):num1
和 num2
,分别代表两个要计算的数字。在命令执行时,读取这两个标志的值,进行加法运算,并打印结果。
可以尝试以下命令执行:
# 使用 greet 命令
go run main.go greet
go run main.go greet Alice
# 使用 calculate 命令
go run main.go calculate --num1 10 --num2 5
命令的定义
为什么要定义命令?
因为大部分命令行工具都是可以进行多种操作的,比如对于数据库命令行工具来说,最基本是需要支持增删改查,那么至少应该支持 create
、put
、get
、delete
等命令,然后才是每个命令的参数。
在 cli
里面使用 &cli.Command{}
来定义命令,然后在最后将所有定义的命令添加到主应用中:
// 将命令添加到应用中
app.Commands = []*cli.Command{greetCommand, calculateCommand}
当然,如果你的命令行工具做的事情很单一,就不需要去定义命令了,直接去定义参数就行。
标志参数的定义
所谓标志参数就是在某个 flag
后面的参数,比如 --name xiaoming
就是将 xiaoming
这个参数传给 name
。
标志参数是有类型的,在命令行中获取标志参数的方式是直接通过标志名称来获取,比如获取一个字符串类型的参数:
name:= ctx.String("name")
标志参数的位置可以随意放置,只要保证标志后面接参数值即可(参数值也有时候可以省略)。
位置参数的定义
位置参数很好理解,在命令行中按照顺序排列的参数就是位置参数,位置参数可以添加很多,但是用不用取决于代码中的读取。
位置参数是一个接口类型:
type Args interface {
// Get returns the nth argument, or else a blank string
Get(n int) string
// First returns the first argument, or else a blank string
First() string
// Tail returns the rest of the arguments (not the first one)
// or else an empty string slice
Tail() []string
// Len returns the length of the wrapped slice
Len() int
// Present checks if there are any arguments present
Present() bool
// Slice returns a copy of the internal slice
Slice() []string
}
获取第一个参数:
name := c.Args().First()
name := c.Args().Get(0)
也可以直接用切片的索引:
name := c.Args().Slice()[0]
参考资料
项目及文档:
使用 cli 库的参考项目(可参考项目结构):