cli
是一個用於構建命令行程序的庫。咱們以前也介紹過一個用於構建命令行程序的庫cobra
。在功能上來講二者差很少,cobra
的優點是提供了一個腳手架,方便開發。cli
很是簡潔,全部的初始化操做就是建立一個cli.App
結構的對象。經過爲對象的字段賦值來添加相應的功能。linux
cli
與咱們上一篇文章介紹的negroni
是同一個做者urfave。git
cli
須要搭配 Go Modules 使用。建立目錄並初始化:github
$ mkdir cli && cd cli
$ go mod init github.com/darjun/go-daily-lib/cli
複製代碼
安裝cli
庫,有v1
和v2
兩個版本。若是沒有特殊需求,通常安裝v2
版本:golang
$ go get -u github.com/urfave/cli/v2
複製代碼
使用:微信
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
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
應用程序以下:app
func main() {
(&cli.App{}).Run(os.Args)
}
複製代碼
可是這個空白程序沒有什麼用處。咱們的hello world
程序,設置了Name/Usage/Action
。Name
和Usage
都顯示在幫助中,Action
是調用該命令行程序時實際執行的函數,須要的信息能夠從參數cli.Context
獲取。dom
編譯、運行(環境:Win10 + Git Bash):ide
$ go build -o hello
$ ./hello
hello world
複製代碼
除了這些,cli
爲咱們額外生成了幫助信息:函數
$ ./hello --help
NAME: hello - hello world example USAGE: hello [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 (default: false) 複製代碼
經過cli.Context
的相關方法咱們能夠獲取傳給命令行的參數信息:oop
NArg()
:返回參數個數;Args()
:返回cli.Args
對象,調用其Get(i)
獲取位置i
上的參數。示例:
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)
}
}
複製代碼
這裏只是簡單輸出:
$ go run main.go hello world
1: hello
2: world
複製代碼
一個好用的命令行程序怎麼會少了選項呢?cli
設置和獲取選項很是簡單。在cli.App{}
結構初始化時,設置字段Flags
便可添加選項。Flags
字段是[]cli.Flag
類型,cli.Flag
其實是接口類型。cli
爲常見類型都實現了對應的XxxFlag
,如BoolFlag/DurationFlag/StringFlag
等。它們有一些共用的字段,Name/Value/Usage
(名稱/默認值/釋義)。看示例:
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
},
},
Action: func(c *cli.Context) error {
name := "world"
if c.NArg() > 0 {
name = c.Args().Get(0)
}
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)
}
}
複製代碼
上面是一個打招呼的命令行程序,可經過選項lang
指定語言,默認爲英語。設置選項爲非english
的值,使用漢語。若是有參數,使用第一個參數做爲人名,不然使用world
。注意選項是經過c.Type(name)
來獲取的,Type
爲選項類型,name
爲選項名。編譯、運行:
$ go build -o flags
# 默認調用
$ ./flags
hello world
# 設置非英語
$ ./flags --lang chinese
你好 world
# 傳入參數做爲人名
$ ./flags --lang chinese dj
你好 dj
複製代碼
咱們能夠經過./flags --help
來查看選項:
$ ./flags --help
NAME: flags - A new cli application USAGE: flags [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) 複製代碼
除了經過c.Type(name)
來獲取選項的值,咱們還能夠將選項存到某個預先定義好的變量中。只須要設置Destination
字段爲變量的地址便可:
func main() {
var language string
app := &cli.App{
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)
}
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
字段中爲選項設置佔位值,佔位值經過反引號 ` 包圍。只有第一個生效,其餘的維持不變。佔位值有助於生成易於理解的幫助信息:
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)
}
}
複製代碼
設置佔位值以後,幫助信息中,該佔位值會顯示在對應的選項後面,對短選項也是有效的:
$ go build -o placeholder
$ ./placeholder --help
NAME: placeholder - A new cli application USAGE: placeholder [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 (default: false) 複製代碼
選項能夠設置多個別名,設置對應選項的Aliases
字段便可:
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "lang",
Aliases: []string{"language", "l"},
Value: "english",
Usage: "language for the greeting",
},
},
Action: func(c *cli.Context) error {
name := "world"
if c.NArg() > 0 {
name = c.Args().Get(0)
}
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)
}
}
複製代碼
使用--lang chinese
、--language chinese
和-l chinese
效果是同樣的。若是經過不一樣的名稱指定同一個選項,會報錯:
$ go build -o aliase
$ ./aliase --lang chinese
你好 world
$ ./aliase --language chinese
你好 world
$ ./aliase -l chinese
你好 world
$ ./aliase -l chinese --lang chinese
Cannot use two forms of the same flag: l lang
複製代碼
除了經過執行程序時手動指定命令行選項,咱們還能夠讀取指定的環境變量做爲選項的值。只須要將環境變量的名字設置到選項對象的EnvVars
字段便可。能夠指定多個環境變量名字,cli
會依次查找,第一個有值的環境變量會被使用。
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
EnvVars: []string{"APP_LANG", "SYSTEM_LANG"},
},
},
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)
}
}
複製代碼
編譯、運行:
$ go build -o env
$ APP_LANG=chinese ./env
你好
複製代碼
cli
還支持從文件中讀取選項的值,設置選項對象的FilePath
字段爲文件路徑:
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
FilePath: "./lang.txt",
},
},
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)
}
}
複製代碼
在main.go
同級目錄建立一個lang.txt
,輸入內容chinese
。而後編譯運行程序:
$ go build -o file
$ ./file
你好
複製代碼
cli
還支持從YAML/JSON/TOML
等配置文件中讀取選項值,這裏就不一一介紹了。
上面咱們介紹了幾種設置選項值的方式,若是同時有多個方式生效,按照下面的優先級從高到低設置:
咱們時常會遇到有多個短選項的狀況。例如 linux 命令ls -a -l
,能夠簡寫爲ls -al
。cli
也支持短選項合寫,只須要設置cli.App
的UseShortOptionHandling
字段爲true
便可:
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: "serve", 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("serve:", c.Bool("serve"))
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)
}
}
複製代碼
編譯運行:
$ go build -o short
$ ./short short -som "some message"
serve: true option: true message: true 複製代碼
須要特別注意一點,設置UseShortOptionHandling
爲true
以後,咱們不能再經過-
指定選項了,這樣會產生歧義。例如-lang
,cli
不知道應該解釋爲l/a/n/g
4 個選項仍是lang
1 個。--
仍是有效的。
若是將選項的Required
字段設置爲true
,那麼該選項就是必要選項。必要選項必須指定,不然會報錯:
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)
}
}
複製代碼
不指定選項lang
運行:
$ ./required
2020/06/23 22:11:32 Required flag "lang" not set
複製代碼
默認狀況下,幫助文本中選項的默認值顯示爲Value
字段值。有些時候,Value
並非實際的默認值。這時,咱們能夠經過DefaultText
設置:
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.IntFlag{
Name: "port",
Value: 0,
Usage: "Use a randomized port",
DefaultText :"random",
},
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
複製代碼
上面代碼邏輯中,若是Value
設置爲 0 就隨機一個端口,這時幫助信息中default: 0
就容易產生誤解了。經過DefaultText
能夠避免這種狀況:
$ go build -o default-text
$ ./default-text --help
NAME: default-text - A new cli application USAGE: default-text [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
字段,便可添加子命令。很是方便!
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: "options 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("removed task template: ", c.Args().First())
return nil
},
},
},
},
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
複製代碼
上面定義了 3 個命令add/complete/template
,template
命令定義了 2 個子命令add/remove
。編譯、運行:
$ go build -o subcommand
$ ./subcommand add dating
added task: dating
$ ./subcommand complete dating
completed task: dating
$ ./subcommand template add alarm
new task template: alarm
$ ./subcommand template remove alarm
removed task template: alarm
複製代碼
注意一點,子命令默認不顯示在幫助信息中,須要顯式調用子命令所屬命令的幫助(./subcommand template --help
):
$ ./subcommand --help
NAME: subcommand - A new cli application USAGE: subcommand [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 options 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 NAME: subcommand template - options for task templates USAGE: subcommand 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) 複製代碼
在子命令數量不少的時候,能夠設置Category
字段爲它們分類,在幫助信息中會將相同分類的命令放在一塊兒展現:
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)
}
}
複製代碼
編譯、運行:
$ go build -o categories
$ ./categories --help
NAME: categories - A new cli application USAGE: categories [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) 複製代碼
看上面的COMMANDS
部分。
在cli
中全部的幫助信息文本均可以自定義,整個應用的幫助信息模板經過AppHelpTemplate
指定。命令的幫助信息模板經過CommandHelpTemplate
設置,子命令的幫助信息模板經過SubcommandHelpTemplate
設置。甚至能夠經過覆蓋cli.HelpPrinter
這個函數本身實現幫助信息輸出。下面程序在默認的幫助信息後添加我的網站和微信信息:
func main() {
cli.AppHelpTemplate = fmt.Sprintf(`%s WEBSITE: http://darjun.github.io WECHAT: GoUpUp`, cli.AppHelpTemplate)
(&cli.App{}).Run(os.Args)
}
複製代碼
編譯運行:
$ go build -o help
$ ./help --help
NAME: help - A new cli application USAGE: help [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 (default: false) WEBSITE: http://darjun.github.io WECHAT: GoUpUp 複製代碼
咱們還能夠改寫整個模板:
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{
Authors: []*cli.Author{
{
Name: "dj",
Email: "darjun@126.com",
},
},
}
app.Run(os.Args)
}
複製代碼
{{.XXX}}
其中XXX
對應cli.App{}
結構中設置的字段,例如上面Authors
:
$ ./help --help
NAME: help - A new cli application USAGE: help [global options] command [command options] [arguments...] AUTHOR: dj <darjun@126.com> COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --help, -h show help (default: false) 複製代碼
注意觀察AUTHOR
部分。
經過覆蓋HelpPrinter
,咱們能本身輸出幫助信息:
func main() {
cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
fmt.Println("Simple help!")
}
(&cli.App{}).Run(os.Args)
}
複製代碼
編譯、運行:
$ ./help --help
Simple help!
複製代碼
默認狀況下,幫助選項爲--help/-h
。咱們能夠經過cli.HelpFlag
字段設置:
func main() {
cli.HelpFlag = &cli.BoolFlag{
Name: "haaaaalp",
Aliases: []string{"halp"},
Usage: "HALP",
}
(&cli.App{}).Run(os.Args)
}
複製代碼
查看幫助:
$ go run main.go --halp
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: --haaaaalp, --halp HALP (default: false) 複製代碼
默認版本選項-v/--version
輸出應用的版本信息。咱們能夠經過cli.VersionFlag
設置版本選項 :
func main() {
cli.VersionFlag = &cli.BoolFlag{
Name: "print-version",
Aliases: []string{"V"},
Usage: "print only the version",
}
app := &cli.App{
Name: "version",
Version: "v1.0.0",
}
app.Run(os.Args)
}
複製代碼
這樣就能夠經過指定--print-version/-V
輸出版本信息了。運行:
$ go run main.go --print-version
version version v1.0.0
$ go run main.go -V
version version v1.0.0
複製代碼
咱們還能夠經過設置cli.VersionPrinter
字段控制版本信息的輸出內容:
const (
Revision = "0cebd6e32a4e7094bbdbf150a1c2ffa56c34e91b"
)
func main() {
cli.VersionPrinter = func(c *cli.Context) {
fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision)
}
app := &cli.App{
Name: "version",
Version: "v1.0.0",
}
app.Run(os.Args)
}
複製代碼
上面程序同時輸出版本號和git
提交的 SHA 值:
$ go run main.go -v
version=v1.0.0 revision=0cebd6e32a4e7094bbdbf150a1c2ffa56c34e91b
複製代碼
cli
很是靈活,只須要設置cli.App
的字段值便可實現相應的功能,不須要額外記憶函數、方法。另外cli
還支持 Bash 自動補全的功能,對 zsh 的支持也比較好,感興趣可自行探索。
你們若是發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue😄
個人博客:darjun.github.io
歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~