Go 语言的程序编译之后是一个可执行的二进制文件,这个特性使得 Go 非常适合做命令行工具,因为一套代码只需要在不同系统上面编译,就可以在所有系统上直接运行,无需运行环境的安装。这篇博文记录 Go 比较流行的命令行工具的使用心得。

cli 库的使用

源码地址:https://github.com/urfave/cli

一个多命令的案例

这个示例的工具名为 mycli,它支持两个命令:greetcalculate

首先,需要安装 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 应用,设置了应用的名称、用途和版本信息。

然后,定义了两个命令:greetcalculate。每个命令都有一个 NameAliasesUsageActionAction 字段指定了命令被执行时的操作。

对于 greet 命令,这里简单地获取第一个参数作为名字,并输出 "Hello, {name}"。如果没有提供参数,则默认使用 "World"。

对于 calculate 命令,这里定义了两个整数类型的标志(flags):num1num2,分别代表两个要计算的数字。在命令执行时,读取这两个标志的值,进行加法运算,并打印结果。

可以尝试以下命令执行:

# 使用 greet 命令
go run main.go greet
go run main.go greet Alice

# 使用 calculate 命令
go run main.go calculate --num1 10 --num2 5

命令的定义

为什么要定义命令?

因为大部分命令行工具都是可以进行多种操作的,比如对于数据库命令行工具来说,最基本是需要支持增删改查,那么至少应该支持 createputgetdelete 等命令,然后才是每个命令的参数。

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 库的参考项目(可参考项目结构):