ZetCode

Go urfave/cli

最后修改时间 2024 年 4 月 11 日

在本文中,我们将展示如何使用 urfave/cli 包在 Golang 中创建命令行工具。

urfave/cli 是一个简单快速的包,用于在 Go 中构建命令行应用程序。它支持命令、子命令、标志、自动帮助系统、动态 shell 补全和 markdown 文档生成。

简单的 CLI 示例

下面的示例演示了如何创建一个简单的 CLI 应用程序骨架。

main.go
package main

import (
     "fmt"
     "log"
     "os"

     "github.com/urfave/cli"
)

func main() {

     app := cli.NewApp()
     app.Name = "my cli application"

     app.Action = (func(ctx *cli.Context) error {
          fmt.Println("app launched")
          return nil
     })

     err := app.Run(os.Args)

     if err != nil {
          log.Fatal(err)
     }
}

该程序运行时会显示一条简短的消息。

import (
     "fmt"
     "log"
     "os"

     "github.com/urfave/cli"
)

导入了该包。

app := cli.NewApp()

使用 cli.NewApp 创建了一个新的 CLI 应用程序。它为应用程序设置了一些默认值。

app.Name = "my cli application"

我们通过 Name 字段为应用程序设置名称。

app.Action = (func(ctx *cli.Context) error {
     fmt.Println("app launched")
     return nil
})

Action 字段被设置为一个函数,当没有指定子命令时会调用该函数。

err := app.Run(os.Args)

Run 函数解析参数切片并将它们路由到正确的标志/参数组合。

$ go build
$ simple.exe
app launched

我们构建并运行该应用程序。

$ simple.exe -h
NAME:
   my cli application - A new cli application

USAGE:
   simple.exe [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

urfave/cli 会自动为我们的工具创建帮助信息。

CLI 参数

通过 Args 函数可以检索传递给程序的参数。

main.go
package main

import (
     "fmt"
     "log"
     "os"

     "github.com/urfave/cli/v2"
)

func main() {

     app := &cli.App{
          Usage: "app a1 a2 ...",
          Action: func(ctx *cli.Context) error {

               n := ctx.NArg()
               fmt.Printf("app received %d arguments\n", n)

               if n >= 2 {

                    first := ctx.Args().First()
                    rest := ctx.Args().Tail()

                    fmt.Printf("first argument: %s\n", first)
                    fmt.Printf("the remaining arguments: %v\n", rest)
               } else if n == 1 {

                    first := ctx.Args().First()
                    fmt.Printf("first argument: %s\n", first)
               }

               return nil
          },
     }

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

该程序检索传递的参数。

n := ctx.NArg()
fmt.Printf("app received %d arguments\n", n)

NArg 返回传递的命令行参数的数量。

first := ctx.Args().First()
rest := ctx.Args().Tail()

First 函数返回第一个参数。Tail 返回其余参数(除第一个以外的所有参数)。

$ nargs.exe 1 2 3 4
app received 4 arguments
first argument: 1
the remaining arguments: [2 3 4]

检查参数

使用 Present 函数,我们可以检查是否向程序传递了任何参数。

main.go
package main

import (
     "fmt"
     "log"
     "os"

     "github.com/urfave/cli/v2"
)

func main() {
     app := &cli.App{
          Action: func(ctx *cli.Context) error {

               if ctx.Args().Present() {

                    fmt.Println(ctx.App.Name)
                    fmt.Println(ctx.NArg())
                    fmt.Println(ctx.Args().First())
                    fmt.Println(ctx.Args().Tail())
               } else {
                    fmt.Println("No arguments specified")
               }

               return nil
          },
     }

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

如果没有参数,应用程序将打印“未指定参数”消息。

$ checkargs.exe 1 2 3 4
checkargs.exe
4
1
[2 3 4]
$ checkargs.exe
No arguments specified

Get 函数

Get 函数返回第 n 个参数,否则返回一个空字符串。

main.go
package main

import (
     "fmt"
     "log"
     "os"
     "time"

     "github.com/urfave/cli/v2"
)

func main() {
     app := &cli.App{
          Name:  "app",
          Usage: "app [now] [hello]",
          Action: func(ctx *cli.Context) error {

               a := ctx.Args().Get(0)

               if a == "now" {
                    now := time.Now()
                    fmt.Println(now)
               } else if a == "hello" {
                    fmt.Println("hello there!")
               } else {

                    fmt.Printf("Usage: %s\n", ctx.App.Usage)
               }

               return nil
          },
     }

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

在程序中,我们检索第一个命令行参数,并根据参数是否等于“now”或“hello”来执行操作。

a := ctx.Args().Get(0)

我们使用 Get 检索第一个参数。在这种情况下,我们也可以使用 First

if a == "now" {
     now := time.Now()
     fmt.Println(now)
} else if a == "hello" {
     fmt.Println("hello there!")
} else {

     fmt.Printf("Usage: %s\n", ctx.App.Usage)
}

如果参数等于“now”,我们打印当前日期时间。如果等于“hello”,我们打印一条消息。否则,将使用 ctx.App.Usage 打印用法。

$ app.exe now
2023-10-03 12:34:08.5846632 +0200 CEST m=+0.003355301
$ app.exe hello
hello there!

CLI 命令

命令是工具执行的特定操作。前面的示例有两个命令:nowhello。现在我们重写程序以使用命令。

main.go
package main

import (
     "fmt"
     "log"
     "os"
     "time"

     "github.com/urfave/cli/v2"
)

func main() {
     app := &cli.App{
          Name: "app",
          Commands: []*cli.Command{
               {
                    Name:  "now",
                    Usage: "Show current local datetime",
                    Action: func(c *cli.Context) error {
                         now := time.Now()
                         fmt.Println(now)
                         return nil
                    },
               },
               {
                    Name:  "hello",
                    Usage: "Show hello message",
                    Action: func(c *cli.Context) error {
                         fmt.Println("Hello there!")
                         return nil
                    },
               },
          },
     }

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

命令在 Commands 字段中指定。

{
     Name:  "now",
     Usage: "Show current local datetime",
     Action: func(c *cli.Context) error {
          now := time.Now()
          fmt.Println(now)
          return nil
     },
},

我们为命令指定了 NameUsageAction

CLI 标志

标志用于将值传递给命令行应用程序。

-count=x
-count x
--count=x
--count x

有几种指定标志的方法。另请注意,布尔标志不需要值。

main.go
package main

import (
     "fmt"
     "log"
     "os"

     "github.com/urfave/cli/v2"
)

func main() {
     app := &cli.App{
          Flags: []cli.Flag{
               &cli.StringFlag{Name: "name"},
          },
          Action: func(ctx *cli.Context) error {

               name := ctx.Value("name")
               fmt.Println(name)

               return nil
          },
     }

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

该程序接受一个 name 标志。

Flags: []cli.Flag{
     &cli.StringFlag{Name: "name"},
},

标志通过 Flags 字段指定。name 标志是一个 StringFlag

Action: func(ctx *cli.Context) error {

     name := ctx.Value("name")
     fmt.Println(name)

     return nil
},

Action 函数中,我们使用 Value 获取标志的值。

$ flag.exe --name "John Doe"
John Doe

别名

我们可以为我们的标志指定别名。别名是标志的另一个名称。

main.go
package main

import (
     "fmt"
     "log"
     "os"

     "github.com/urfave/cli/v2"
)

func main() {
     app := &cli.App{
          Flags: []cli.Flag{
               &cli.StringFlag{Name: "config", Aliases: []string{"cfg", "conf"}},
          },
          Action: func(ctx *cli.Context) error {

               fmt.Println(ctx.String("config"))
               fmt.Println(ctx.String("cfg"))
               fmt.Println(ctx.String("conf"))

               return nil
          },
     }

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

config 标志有 cfgconf 别名。

Flags: []cli.Flag{
     &cli.StringFlag{Name: "config", Aliases: []string{"cfg", "conf"}},
},

别名通过 Aliases 选项指定。

fmt.Println(ctx.String("config"))
fmt.Println(ctx.String("cfg"))
fmt.Println(ctx.String("conf"))

我们通过所有三个选项检索值。

$ aliases.exe -cfg file.txt
file.txt
file.txt
file.txt

目标

Destination 选项可用于指定将要复制传递值变量。

main.go
package main

import (
     "fmt"
     "log"
     "os"

     "github.com/urfave/cli/v2"
)

func main() {

     var name string

     app := &cli.App{
          Flags: []cli.Flag{
               &cli.StringFlag{Name: "name", Destination: &name},
          },
          Action: func(ctx *cli.Context) error {

               msg := fmt.Sprintf("Hello %s!", name)
               fmt.Println(msg)

               return nil
          },
     }

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

在程序中,我们将 name 参数的值复制到 name 变量。

应用程序版本

应用程序版本可以通过 VersionVersionFlag 设置。

main.go
package main

import (
     "os"

     "github.com/urfave/cli/v2"
)

func main() {
     cli.VersionFlag = &cli.BoolFlag{
          Name:    "version",
          Aliases: []string{"V"},
          Usage:   "shows the app version",
     }

     app := &cli.App{
          Name:    "app",
          Usage:   "app demonstrating version",
          Version: "v1.0",
     }
     app.Run(os.Args)
}

在示例中,我们设置了应用程序的版本。

cli.VersionFlag = &cli.BoolFlag{
     Name:    "version",
     Aliases: []string{"V"},
     Usage:   "shows the app version",
}

我们指定了标志名称、别名和用法。使用了 BoolFlag

app := &cli.App{
     Name:    "app",
     Usage:   "app demonstrating version",
     Version: "v1.0",
}

我们指定了版本字符串。

$ app.exe --version
app version v1.0

来源

Go urfave/cli - Github 页面

在本文中,我们展示了如何使用 urfave/cli 包创建 Go 命令行工具。

作者

我的名字是 Jan Bodnar,我是一名充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程