Introduction to ##
cli Is a library for building command line programs.We've previously described a library for building command line programs cobra .Functionally, they are similar, and cobra has the advantage of providing a scaffold for easy development.cli is very simple, all initialization is to create oneCli.AppThe object of the structure.Add the appropriate functionality by assigning values to the field of the object.
cli is the same author as negroni in our last article urfave.
Quick use
cli needs to be used with Go Modules.Create directory and initialize:
$ mkdir cli && cd cli $ go mod init github.com/darjun/go-daily-lib/cli
Install cli library with v1 and v2 versions.If there are no special requirements, install v2 version:
$ go get -u github.com/urfave/cli/v2
Use:
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) } }
It's very easy to use, create one in theoryCli.AppThe object of the structure is then called its Run() method, passing in the parameters of the command line.A blank cli application is as follows:
func main() { (&cli.App{}).Run(os.Args) }
But this blank program is of little use.Our hello world program, set up Name/Usage/Action.Both Name and Usage are shown in the help. Action is the function actually executed when the command line program is called. The information needed can be obtained from the parametersCli.ContextObtain.
Compile, run (environment: Win10 + Git Bash):
$ go build -o hello $ ./hello hello world
In addition to this, cli generates additional help information for us:
$ ./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)
parameter
adoptCli.ContextRelated methods allow us to get parameter information passed to the command line:
- NArg(): Returns the number of parameters;
- Args(): BackCli.ArgsObject that calls its Get(i) to get the parameter at position i.
Example:
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) } }
Here's just a simple output:
$ go run main.go hello world 1: hello 2: world
option
How come there are fewer options for a useful command line program?cli settings and get options are simple.stayCli.AppWhen the {} structure is initialized, you can add options by setting the field Flags.The Flags field is []cli.FlagType,cli.FlagIt is actually an interface type.cli implements corresponding XxFlags for common types, such as BoolFlag/DurationFlag/StringFlag.They have some fields in common, Name/Value/Usage (name/default/definition).See an example:
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("Hello", name) } return nil }, } err := app.Run(os.Args) if err != nil { log.Fatal(err) } }
The above is a greeting command line program that specifies the language with the option lang and defaults to English.Set the option to a value other than english, using Chinese.If there are parameters, use the first parameter as the person name, otherwise use the world.Note that options are acquired through c.Type(name), Type is the option type, and Name is the option name.Compile, run:
$ go build -o flags # Default Call $ ./flags hello world # Set up non-English $ ./flags --lang chinese //Hello world # Incoming parameter as person name $ ./flags --lang chinese dj //Hello dj
We can see the options through. /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)
Input variable
In addition to getting the value of the option through c.Type(name), we can also save the option in a predefined variable.Simply set the address of the Destination field as a variable:
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("Hello", name) } return nil }, } err := app.Run(os.Args) if err != nil { log.Fatal(err) } }
The effect is the same as that of the above program.
Placeholder value
cli can set placeholders for options in the Usage field, surrounded by inverted quotes `.Only the first one will take effect and the other will remain unchanged.Placeholder values help generate easy-to-understand help information:
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) } }
After setting a placeholder value, the placeholder value is displayed in the help message after the corresponding option, which is also valid for short options:
$ 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)
alias
Options can have multiple Aliases, and the Alliases field of the corresponding option can be set:
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("Hello", name) } return nil }, } err := app.Run(os.Args) if err != nil { log.Fatal(err) } }
The effect is the same with -lang chinese, --language chinese, and -l chinese.If you specify the same option with a different name, you will get an error:
$ go build -o aliase $ ./aliase --lang chinese //Hello world $ ./aliase --language chinese //Hello world $ ./aliase -l chinese //Hello world $ ./aliase -l chinese --lang chinese Cannot use two forms of the same flag: l lang
environment variable
In addition to manually specifying command line options when executing the program, we can also read the specified environment variables as the values for the options.Simply set the name of the environment variable to the EnvVars field of the option object.You can specify multiple environment variable names, cli looks for them in turn, and the first value environment variable is used.
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("Hello") } return nil }, } err := app.Run(os.Args) if err != nil { log.Fatal(err) } }
Compile, run:
$ go build -o env $ APP_LANG=chinese ./env //Hello
file
cli also supports reading the value of an option from a file, setting the FilePath field of the option object to the file path:
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("Hello") } return nil }, } err := app.Run(os.Args) if err != nil { log.Fatal(err) } }
stayMain.goCreate a sibling directoryLang.txt, enter the content chinese.Then compile and run the program:
$ go build -o file $ ./file //Hello
cli also supports reading option values from configuration files such as YAML/JSON/TOML, which are not covered here.
Option Priority
We have described several ways to set option values above. If more than one way works at the same time, set them from high to low with the following priority:
- User-specified command line option values;
- Environment variables;
- Configuration files;
- Default value of option.
Combine Short Options
We often encounter situations where there are several short options.For example, the linux command ls-a-l can be abbreviated as ls-a L.cli also supports short option writing, only settingCli.AppThe UseShortOptionHandling field of true is sufficient:
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) } }
Compile and run:
$ go build -o short $ ./short short -som "some message" serve: true option: true message: true
It is important to note that once UseShortOptionHandling is set to true, we can no longer specify the option through - which can create ambiguity.For example, -l a n g, cli does not know if it should be interpreted as l/a/n/g with four options or l a n g with one.--still valid.
Required Options
If the equired field of the option is set to true, then that option is necessary.Necessary options must be specified or error will occur:
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("Hello") } return nil }, } err := app.Run(os.Args) if err != nil { log.Fatal(err) } }
Run without specifying the option lang:
$ ./required 2020/06/23 22:11:32 Required flag "lang" not set
Default value in help text
By default, the default value of the option in the help text is displayed as the Value field value.Sometimes Value is not the actual default value.At this point, we can set it up with 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) } }
In the code logic above, if the Value is set to 0 and a port is randomly assigned, default: 0 in the help message is easily misinterpreted.This can be avoided with 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)
Subcommand
Subcommand missions make line programs more organized.Git has a large number of commands, many of which exist as subcommands under a command.For example, git remote command has add/rename/remove subcommands, git submodule has add/status/init/update subcommands.
cli by settingCli.AppSubcommands can be added by adding commands to the Commands field and setting the SubCommands field for each command.Very convenient!
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) } }
The template command defines two subcommands, add/remove.Compile, run:
$ 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
Note that subcommands do not appear in the help information by default and require help to explicitly invoke the command to which the subcommand belongs (. /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)
classification
When there are a large number of subcommands, you can set the Category field to categorize them and display the same categorized commands together in the help information:
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) } }
Compile, run:
$ 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)
Look at the COMMANDS section above.
Custom Help Information
All help information text in cli can be customized, and the help information template for the entire application is specified by AppHelpTemplate.Help information templates for commands are set through CommandHelpTemplate, and help information templates for subcommands are set through SubcommandHelpTemplate.It can even be overwrittenCli.HelpPrinterThis function outputs help information on its own.The following program adds personal website and WeChat information after the default help information:
func main() { cli.AppHelpTemplate = fmt.Sprintf(`%s WEBSITE: http://darjun.github.io WECHAT: GoUpUp`, cli.AppHelpTemplate) (&cli.App{}).Run(os.Args) }
Compile and run:
$ 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
We can also rewrite the entire template:
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) }
Where XXX correspondsCli.AppFields set in the {} structure, such as Authors above:
$ ./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)
Note the AUTHOR section.
By overwriting HelpPrinter, we can output help information ourselves:
func main() { cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { fmt.Println("Simple help!") } (&cli.App{}).Run(os.Args) }
Compile, run:
$ ./help --help Simple help!
Built-in Options
Help Options
By default, the help option is--help/-h.We can passCli.HelpFlagField settings:
func main() { cli.HelpFlag = &cli.BoolFlag{ Name: "haaaaalp", Aliases: []string{"halp"}, Usage: "HALP", } (&cli.App{}).Run(os.Args) }
View help:
$ 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)
Version Options
The default version option - v/--version outputs the version information of the application.We can passCli.VersionFlagSet version options:
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) }
This allows you to output version information by specifying--print-version/-V.Function:
$ go run main.go --print-version version version v1.0.0 $ go run main.go -V version version v1.0.0
We can also set it upCli.VersionPrinterField controls the output of version information:
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) }
The above program outputs both version number and SHA value submitted by git:
$ go run main.go -v version=v1.0.0 revision=0cebd6e32a4e7094bbdbf150a1c2ffa56c34e91b
summary
Cli is very flexible and only needs setupCli.AppThe field value of can achieve the corresponding function without additional memory function and method.In addition, cli supports Bash auto-completion, and zsh is better supported, so you can explore it yourself if you are interested.
If you find a fun and useful GoLanguage Library, you are welcome to submit your issue on GitHub, the GoDaily Library_
Reference resources
- cli GitHub: https://github.com/urfave/cli
- Go Daily Library of cobra: https://darjun.github.io/2020/01/17/godailylib/cobra/
- Go Daily Library of GitHub: https://github.com/darjun/go-daily-lib
I
My blog: https://darjun.github.io
Welcome to my WeChat Public Number GoUpUp to learn and progress together.