本文共 17215 字,大约阅读时间需要 57 分钟。
golang flag 包
Command-line utilities are rarely useful out of the box without additional configuration. Good defaults are important, but useful utilities need to accept configuration from users. On most platforms, command-line utilities accept flags to customize the command’s execution. Flags are key-value delimited strings added after the name of the command. Go lets you craft command-line utilities that accept flags by using the flag
package from the standard library.
如果没有其他配置,命令行实用程序很少会立即可用。 好的默认值很重要,但是有用的实用程序需要接受用户的配置。 在大多数平台上,命令行实用程序接受标志以自定义命令的执行。 标志是在命令名称后添加的键值分隔字符串。 Go使您可以使用标准库中的flag
包来制作接受标志的命令行实用程序。
In this tutorial you’ll explore various ways to use the flag
package to build different kinds of command-line utilities. You’ll use a flag to control program output, introduce positional arguments where you mix flags and other data, and then implement sub-commands.
在本教程中,您将探索使用flag
包构建各种命令行实用程序的各种方法。 您将使用标志来控制程序输出,在混合标志和其他数据的位置引入位置参数,然后实现子命令。
Using the flag
package involves three steps: First, to capture flag values, then define the flags your Go application will use, and finally, parse the flags provided to the application upon execution. Most of the functions within the flag
package are concerned with defining flags and binding them to variables that you have defined. The parsing phase is handled by the Parse()
function.
使用flag
包涉及三个步骤:首先, 以捕获标志值,然后定义Go应用程序将使用的标志,最后,在执行时解析提供给应用程序的标志。 flag
包中的大多数功能都与定义标志并将其绑定到您定义的变量有关。 解析阶段由Parse()
函数处理。
To illustrate, you’ll create a program that defines a flag that changes the message that will be printed to standard output. If there’s a -color
flag provided, the program will print a message in blue. If no flag is provided, the message will be printed without any color.
为了说明这一点,您将创建一个程序,该程序定义一个标志,该标志会更改将打印到标准输出的消息。 如果提供了-color
标志,则程序将以蓝色打印一条消息。 如果未提供标志,则将打印没有任何颜色的消息。
Create a new file called boolean.go
:
创建一个名为boolean.go
的新文件:
Add the following code to the file to create the program:
将以下代码添加到文件中以创建程序:
package mainimport ( "flag" "fmt")type Color stringconst ( ColorBlack Color = "\u001b[30m" ColorRed = "\u001b[31m" ColorGreen = "\u001b[32m" ColorYellow = "\u001b[33m" ColorBlue = "\u001b[34m" ColorReset = "\u001b[0m")func colorize(color Color, message string) { fmt.Println(string(color), message, string(ColorReset))}func main() { useColor := flag.Bool("color", false, "display colorized output") flag.Parse() if *useColor { colorize(ColorBlue, "Hello, DigitalOcean!") return } fmt.Println("Hello, DigitalOcean!")}
This example uses to instruct the terminal to display colorized output. These are specialized sequences of characters, so it makes sense to define a new type for them. In this example, we’ve called that type Color
, and defined the type as a string
. We then define a palette of colors to use in the const
block that follows. The colorize
function defined after the const
block accepts one of these Color
constants and a string
variable for the message to colorize. It then instructs the terminal to change color by first printing the escape sequence for the color requested, then prints the message, and finally requests that the terminal reset its color by printing the special color reset sequence.
本示例使用来指示终端显示彩色输出。 这些是特殊的字符序列,因此有必要为其定义一个新的类型。 在此示例中,我们将该类型称为Color
,并将该类型定义为string
。 然后,我们定义要在随后的const
块中使用的调色板。 在const
块之后定义的colorize
函数接受这些Color
常量之一和string
变量以使消息着色。 然后,它指示终端通过先打印所请求的颜色的转义序列来更改颜色,然后打印消息,最后请求终端通过打印特殊的颜色重置序列来重置其颜色。
Within main
, we use the flag.Bool
function to define a Boolean flag called color
. The second parameter to this function, false
, sets the default value for this flag when it is not provided. Contrary to expectations you may have, setting this to true
does not invert the behavior such that providing a flag will cause it to become false. Consequently, the value of this parameter is almost always false
with Boolean flags.
在main
内部,我们使用flag.Bool
函数定义一个称为color
的布尔值标志。 此函数的第二个参数false
,在未提供此标志时设置默认值。 与您可能期望的相反,将其设置为true
不会反转行为,以致提供一个标志将导致它变为false。 因此,带有布尔标志的此参数的值几乎始终为false
。
The final parameter is a string of documentation that can be printed as a usage message. The value returned from this function is a pointer to a bool
. The flag.Parse
function on the next line uses this pointer to set the bool
variable based on the flags passed in by the user. We are then able to check the value of this bool
pointer by dereferencing the pointer. More information about pointer variables can be found in the . Using this Boolean value, we can then call colorize
when the -color
flag is set, and call the fmt.Println
variable when the flag is absent.
最后一个参数是可以作为使用情况消息打印的文档字符串。 从该函数返回的值是一个指向bool
的指针。 下一行的flag.Parse
函数使用此指针根据用户传递的标志来设置bool
变量。 然后,我们可以通过取消引用指针来检查此bool
指针的值。 有关指针变量的更多信息,请参见 。 使用这种布尔值,我们可以调用colorize
时-color
标志设置,并调用fmt.Println
变量当标志是不存在的。
Save the file and run the program without any flags:
保存文件并运行程序而没有任何标志:
You’ll see the following output:
您将看到以下输出:
Output Hello, DigitalOcean!
Now run this program again with the -color
flag:
现在,使用-color
标志再次运行该程序:
The output will be the same text, but this time in the color blue.
输出将是相同的文本,但是这次是蓝色。
Flags are not the only values passed to commands. You might also send file names or other data.
标志不是传递给命令的唯一值。 您可能还会发送文件名或其他数据。
Typically commands will take a number of arguments that act as the subject of the command’s focus. For example, the head
command, which prints the first lines of a file, is often invoked as head example.txt
. The file example.txt
is a positional argument in the invocation of the head
command.
通常,命令将使用许多参数,这些参数充当命令焦点的主题。 例如,经常将打印文件第一行的head
命令作为head example.txt
调用。 文件example.txt
是head
命令调用中的位置参数。
The Parse()
function will continue to parse flags that it encounters until it detects a non-flag argument. The flag
package makes these available through the Args()
and Arg()
functions.
Parse()
函数将继续解析遇到的标志,直到检测到非标志参数为止。 flag
包通过Args()
和Arg()
函数使它们可用。
To illustrate this, you’ll build a simplified re-implementation of the head
command, which displays the first several lines of a given file:
为了说明这一点,您将构建简化的head
命令重新实现,该命令显示给定文件的前几行:
Create a new file called head.go
and add the following code:
创建一个名为head.go
的新文件,并添加以下代码:
package mainimport ( "bufio" "flag" "fmt" "io" "os")func main() { var count int flag.IntVar(&count, "n", 5, "number of lines to read from the file") flag.Parse() var in io.Reader if filename := flag.Arg(0); filename != "" { f, err := os.Open(filename) if err != nil { fmt.Println("error opening file: err:", err) os.Exit(1) } defer f.Close() in = f } else { in = os.Stdin } buf := bufio.NewScanner(in) for i := 0; i < count; i++ { if !buf.Scan() { break } fmt.Println(buf.Text()) } if err := buf.Err(); err != nil { fmt.Fprintln(os.Stderr, "error reading: err:", err) }}
First, we define a count
variable to hold the number of lines the program should read from the file. We then define the -n
flag using flag.IntVar
, mirroring the behavior of the original head
program. This function allows us to pass our own to a variable in contrast to the flag
functions that do not have the Var
suffix. Apart from this difference, the rest of the parameters to flag.IntVar
follow its flag.Int
counterpart: the flag name, a default value, and a description. As in the previous example, we then call flag.Parse()
to process the user’s input.
首先,我们定义一个count
变量来保存程序应从文件中读取的行数。 然后,使用flag.IntVar
定义-n
标志,以镜像原始head
程序的行为。 与不带Var
后缀的flag
函数相比,此函数使我们可以将自己的传递给变量。 除了这种区别之外, flag.IntVar
的其余参数flag.IntVar
跟随其flag.Int
对应项:标志名称,默认值和描述。 与前面的示例一样,然后调用flag.Parse()
来处理用户的输入。
The next section reads the file. We first define an io.Reader
variable that will either be set to the file requested by the user, or standard input passed to the program. Within the if
statement, we use the flag.Arg
function to access the first positional argument after all flags. If the user supplied a file name, this will be set. Otherwise, it will be the empty string (""
). When a filename is present, we use the os.Open
function to open that file and set the io.Reader
we defined before to that file. Otherwise, we use os.Stdin
to read from standard input.
下一节将读取文件。 我们首先定义一个io.Reader
变量,该变量将被设置为用户请求的文件或传递给程序的标准输入。 在if
语句中,我们使用flag.Arg
函数访问所有标志之后的第一个位置参数。 如果用户提供了文件名,则将进行设置。 否则,它将是空字符串( ""
)。 当存在文件名时,我们使用os.Open
函数打开该文件,并将之前定义的io.Reader
设置为该文件。 否则,我们使用os.Stdin
从标准输入读取。
The final section uses a *bufio.Scanner
created with bufio.NewScanner
to read lines from the io.Reader
variable in
. We iterate up to the value of count
using a , calling break
if scanning the line with buf.Scan
produces a false
value, indicating that the number of lines is less than the number requested by the user.
最后一节使用*bufio.Scanner
与创建bufio.NewScanner
读取来自线io.Reader
变量in
。 我们迭代到的值count
使用 ,呼叫break
如果扫描与所述线buf.Scan
产生一个false
值,表示线的数量小于由用户请求的数目。
Run this program and display the contents of the file you just wrote by using head.go
as the file argument:
运行该程序,并使用head.go
作为file参数显示刚编写的文件的内容:
The --
separator is a special flag recognized by the flag
package which indicates that no more flag arguments follow. When you run this command, you receive the following output:
--
分隔符是flag
包识别的特殊标志,指示不再跟随标志参数。 运行此命令时,将收到以下输出:
Output package mainimport ( "bufio" "flag"
Use the -n
flag you defined to adjust the amount of output:
使用您定义的-n
标志来调整输出量:
This outputs only the package statement:
这仅输出package语句:
Output package main
Finally, when the program detects that no positional arguments were supplied, it reads input from standard input, just like head
. Try running this command:
最后,当程序检测到未提供任何位置参数时,它将像head
一样从标准输入中读取输入。 尝试运行以下命令:
You’ll see the output:
您将看到输出:
Output fishlobsterssharks
The behavior of the flag
functions you’ve seen so far has been limited to examining the entire command invocation. You don’t always want this behavior, especially if you’re writing a command line tool that supports sub-commands.
到目前为止,您已经看到的flag
函数的行为仅限于检查整个命令调用。 您并不总是希望这种行为,尤其是在编写支持子命令的命令行工具时。
Modern command-line applications often implement “sub-commands” to bundle a suite of tools under a single command. The most well-known tool that uses this pattern is git
. When examining a command like git init
, git
is the command and init
is the sub-command of git
. One notable feature of sub-commands is that each sub-command can have its own collection of flags.
现代的命令行应用程序通常实现“子命令”,以将一组工具捆绑在一个命令下。 使用此模式的最著名的工具是git
。 当检查git init
类的命令时, git
是命令, init
是git
的子命令。 子命令的一个显着特征是每个子命令可以具有自己的标志集合。
Go applications can support sub-commands with their own set of flags using the flag.(*FlagSet)
type. To illustrate this, create a program that implements a command using two sub-commands with different flags.
Go应用程序可以使用flag.(*FlagSet)
类型来支持带有自己的一组标志的子命令。 为了说明这一点,请创建一个使用两个带有不同标志的子命令来实现命令的程序。
Create a new file called subcommand.go
and add the following content to the file:
创建一个名为subcommand.go
的新文件,并将以下内容添加到该文件中:
package mainimport ( "errors" "flag" "fmt" "os")func NewGreetCommand() *GreetCommand { gc := &GreetCommand{ fs: flag.NewFlagSet("greet", flag.ContinueOnError), } gc.fs.StringVar(&gc.name, "name", "World", "name of the person to be greeted") return gc}type GreetCommand struct { fs *flag.FlagSet name string}func (g *GreetCommand) Name() string { return g.fs.Name()}func (g *GreetCommand) Init(args []string) error { return g.fs.Parse(args)}func (g *GreetCommand) Run() error { fmt.Println("Hello", g.name, "!") return nil}type Runner interface { Init([]string) error Run() error Name() string}func root(args []string) error { if len(args) < 1 { return errors.New("You must pass a sub-command") } cmds := []Runner{ NewGreetCommand(), } subcommand := os.Args[1] for _, cmd := range cmds { if cmd.Name() == subcommand { cmd.Init(os.Args[2:]) return cmd.Run() } } return fmt.Errorf("Unknown subcommand: %s", subcommand)}func main() { if err := root(os.Args[1:]); err != nil { fmt.Println(err) os.Exit(1) }}
This program is divided into a few parts: the main
function, the root
function, and the individual functions to implement the sub-command. The main
function handles errors returned from commands. If any function returns an , the if
statement will catch it, print the error, and the program will exit with a status code of 1
, indicating that an error occurred to the rest of the operating system. Within main
, we pass all of the arguments the program was invoked with to root
. We remove the first argument, which is the name of the program (in the previous examples ./subcommand
) by slicing os.Args
first.
该程序分为几个部分: main
函数, root
函数和实现子命令的单个函数。 main
功能处理命令返回的错误。 如果有任何函数返回 ,则if
语句将捕获该错误,并输出错误,程序将以状态码1
退出,表明操作系统的其余部分均发生了错误。 在main
内部,我们将调用程序的所有参数传递给root
。 我们通过先切片os.Args
删除第一个参数,即程序的名称(在前面的示例中是./subcommand
)。
The root
function defines []Runner
, where all sub-commands would be defined. Runner
is an for sub-commands that allows root
to retrieve the name of the sub-command using Name()
and compare it against the contents subcommand
variable. Once the correct sub-command is located after iterating through the cmds
variable we initialize the sub-command with the rest of the arguments and invoke that command’s Run()
method.
root
函数定义[]Runner
,其中将定义所有子命令。 Runner
是子命令的 ,它允许root
使用Name()
检索子命令的Name()
,并将其与subcommand
变量进行比较。 在迭代cmds
变量后找到正确的子命令后,我们将使用其余参数初始化该子命令,然后调用该命令的Run()
方法。
We only define one sub-command, though this framework would easily allow us to create others. The GreetCommand
is instantiated using NewGreetCommand
where we create a new *flag.FlagSet
using flag.NewFlagSet
. flag.NewFlagSet
takes two arguments: a name for the flag set, and a strategy for reporting parsing errors. The *flag.FlagSet
’s name is accessible using the flag.(*FlagSet).Name
method. We use this in the (*GreetCommand).Name()
method so the name of the sub-command matches the name we gave to the *flag.FlagSet
. NewGreetCommand
also defines a -name
flag in a similar way to previous examples, but it instead calls this as a method off the *flag.FlagSet
field of the *GreetCommand
, gc.fs
. When root
calls the Init()
method of the *GreetCommand
, we pass the arguments provided to the Parse
method of the *flag.FlagSet
field.
我们仅定义一个子命令,尽管此框架可以轻松地使我们创建其他子命令。 使用GreetCommand
实例化NewGreetCommand
,在其中我们使用NewGreetCommand
创建一个新的*flag.FlagSet
flag.NewFlagSet
。 flag.NewFlagSet
具有两个参数:标志集的名称和报告解析错误的策略。 *flag.FlagSet
的名称可以使用flag.(*FlagSet).Name
方法访问。 我们在(*GreetCommand).Name()
方法中使用它,因此子命令的名称与我们给*flag.FlagSet
赋予的名称匹配。 NewGreetCommand
还定义了-name
标志以类似的方式,以前面的例子,但它而不是调用此为关闭的方法*flag.FlagSet
的领域*GreetCommand
, gc.fs
。 当root
调用*GreetCommand
的Init()
方法时,我们会将提供的参数传递给*flag.FlagSet
字段的Parse
方法。
It will be easier to see sub-commands if you build this program and then run it. Build the program:
如果您生成此程序然后运行它,将更容易看到子命令。 生成程序:
Now run the program with no arguments:
现在,运行不带任何参数的程序:
You’ll see this output:
您将看到以下输出:
Output You must pass a sub-command
Now run the program with the greet
sub-command:
现在,使用greet
子命令运行该程序:
This produces the following output:
这将产生以下输出:
Output Hello World !
Now use the -name
flag with greet
to specify a name:
现在,将-name
标志与greet
一起使用以指定名称:
./subcommand greet -name Sammy
./subcommand问候-name Sammy
You’ll see this output from the program:
您将从程序中看到以下输出:
Output Hello Sammy !
This example illustrates some principles behind how larger command line applications could be structured in Go. FlagSet
s are designed to give developers more control over where and how flags are processed by the flag parsing logic.
此示例说明了如何在Go中构造更大的命令行应用程序的一些原理。 FlagSet
旨在使开发人员可以更好地控制标志解析逻辑在何处以及如何处理标志。
Flags make your applications more useful in more contexts because they give your users control over how the programs execute. It’s important to give users useful defaults, but you should give them the opportunity to override settings that don’t work for their situation. You’ve seen that the flag
package offers flexible choices to present configuration options to your users. You can choose a few simple flags, or build an extensible suite of sub-commands. In either case, using the flag
package will help you build utilities in the style of the long history of flexible and scriptable command line tools.
标志使您的应用程序在更多上下文中更有用,因为标志使您的用户可以控制程序的执行方式。 为用户提供有用的默认值很重要,但是您应该给他们机会覆盖对他们的情况不起作用的设置。 您已经看到, flag
包提供了灵活的选择,可以向用户提供配置选项。 您可以选择一些简单的标志,或构建一组可扩展的子命令。 无论哪种情况,使用flag
包都将帮助您构建具有悠久历史的灵活且可编写脚本的命令行工具的样式。
To learn more about the Go programming language, check out our full .
要了解有关Go编程语言的更多信息,请查看我们完整的“ 。
翻译自:
golang flag 包
转载地址:http://fvhgb.baihongyu.com/