golang flag 包_如何在Go中使用Flag包

news/2024/7/3 9:33:37

golang flag 包

介绍 (Introduction)

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 a Flag to Change a Program’s Behavior)

Using the flag package involves three steps: First, define variables 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 Boolean 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的新文件:

  • nano boolean.go

    纳米boolean.go

Add the following code to the file to create the program:

将以下代码添加到文件中以创建程序:

boolean.go
boolean.go
package main

import (
    "flag"
    "fmt"
)

type Color string

const (
    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 ANSI Escape Sequences 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.

本示例使用ANSI转义序列来指示终端显示彩色输出。 这些是特殊的字符序列,因此有必要为其定义一个新的类型。 在此示例中,我们将该类型称为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 tutorial on pointers. 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:

保存文件并运行程序而没有任何标志:

  • go run boolean.go

    去运行boolean.go

You’ll see the following output:

您将看到以下输出:


   
Output
Hello, DigitalOcean!

Now run this program again with the -color flag:

现在,使用-color标志再次运行该程序:

  • go run boolean.go -color

    去运行boolean.go -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.

标志不是传递给命令的唯一值。 您可能还会发送文件名或其他数据。

处理位置参数 (Working with Positional Arguments)

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.txthead命令调用中的位置参数。

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的新文件,并添加以下代码:

head.go
head.go
package main

import (
    "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 pointer 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 for loop, 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使用for循环 ,呼叫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参数显示刚编写的文件的内容:

  • go run head.go -- head.go

    运行head.go-head.go

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 main import ( "bufio" "flag"

Use the -n flag you defined to adjust the amount of output:

使用您定义的-n标志来调整输出量:

  • go run head.go -n 1 head.go

    去运行head.go -n 1 head.go

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一样从标准输入中读取输入。 尝试运行以下命令:

  • echo "fish\nlobsters\nsharks\nminnows" | go run head.go -n 3

    回声“鱼\龙虾\ nsharks \ nminnows” | 去运行head.go -n 3

You’ll see the output:

您将看到输出:


   
Output
fish lobsters sharks

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函数的行为仅限于检查整个命令调用。 您并不总是希望这种行为,尤其是在编写支持子命令的命令行工具时。

使用FlagSet实现子命令 (Using FlagSet to Implement Sub-commands)

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是命令, initgit的子命令。 子命令的一个显着特征是每个子命令可以具有自己的标志集合。

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 main

import (
    "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 error, 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 interface 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.NewFlagSetflag.NewFlagSet具有两个参数:标志集的名称和报告解析错误的策略。 *flag.FlagSet的名称可以使用flag.(*FlagSet).Name方法访问。 我们在(*GreetCommand).Name()方法中使用它,因此子命令的名称与我们给*flag.FlagSet赋予的名称匹配。 NewGreetCommand还定义了-name标志以类似的方式,以前面的例子,但它而不是调用此为关闭的方法*flag.FlagSet的领域*GreetCommandgc.fs 。 当root调用*GreetCommandInit()方法时,我们会将提供的参数传递给*flag.FlagSet字段的Parse方法。

It will be easier to see sub-commands if you build this program and then run it. Build the program:

如果您生成此程序然后运行它,将更容易看到子命令。 生成程序:

  • go build subcommand.go

    转到build subcommand.go

Now run the program with no arguments:

现在,运行不带任何参数的程序:

  • ./subcommand

    ./子命令

You’ll see this output:

您将看到以下输出:


   
Output
You must pass a sub-command

Now run the program with the greet sub-command:

现在,使用greet子命令运行该程序:

  • ./subcommand 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. FlagSets are designed to give developers more control over where and how flags are processed by the flag parsing logic.

此示例说明了如何在Go中构造更大的命令行应用程序的一些原理。 FlagSet旨在使开发人员可以更好地控制标志解析逻辑在何处以及如何处理标志。

结论 (Conclusion)

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 How To Code in Go series.

要了解有关Go编程语言的更多信息,请查看我们完整的“ 如何在Go中编码”系列文章 。

翻译自: https://www.digitalocean.com/community/tutorials/how-to-use-the-flag-package-in-go

golang flag 包


http://www.niftyadmin.cn/n/3649242.html

相关文章

Ubuntu 装 Samba 安装详解 (已在Ubuntu11Server测试通过)

转自&#xff1a;http://wiki.ubuntu.org.cn/Samba Samba Samba是Ubuntu和Windows进行网络共享的工具&#xff0c;比如分享打印机&#xff0c;互相之间传输资料文件。 目录 [隐藏]1 安装Samba2 Kubuntu3 配置4 Ubuntu访问windows xp/2003/2000系统5 排错 5.1 乱码 6 smb用户…

golang接口的使用场景_如何在Go中使用接口

golang接口的使用场景介绍 (Introduction) Writing flexible, reusable, and modular code is vital for developing versatile programs. Working in this way ensures code is easier to maintain by avoiding the need to make the same change in multiple places. How you…

android以欺骗的方法使用隐藏API调用举例(国际化,多语言)

Android对国际化与多语言切换已经做得不错了&#xff0c;一个应用只要命名相应语系的values-[language]文件夹&#xff0c;通过“设置”→“语言&键盘”→“选择语言”即可实现应用多种语言的切换。 但如何在应用里自己实现&#xff1f;搜索过发现网上有如下的做法&…

[收藏]mvm的“公司如船”大论

mvm的&#xff1a;http://home.wangjianshuo.com/mvm/000525.htm公司如船有个企业家被问到他为什么喜欢航海。他的回答是&#xff0c;航海和经营企业有强烈的共同点&#xff1a;到了海上以后&#xff0c;就算船上有人你不喜欢&#xff0c;你也只能去适应他&#xff0c;而不可能…

关于公司系统支撑工作的建议

关于公司系统支撑工作的建议成晓旭刚来部门不久&#xff0c;对部门的整体工作情况了解不多&#xff0c;对公司的信息系统建设情况更是不敢枉自品评。对于像我们这样规模的公司&#xff0c;自己建设、实施和维护满足公司自身管理要求的管理信息系统&#xff0c;是目前部门公司对…

如何将class文件打包成jar文件

如何将class文件打包成jar文件&#xff0c;这是一个很严肃的问题&#xff0c;当你在在使用webservice的saop协议的时候&#xff0c;很多地方不免需要用到远程jar包&#xff0c;这些jar包里面&#xff0c;存放的将是你的VO或者是JavaBean 这些方法都将对外提供并暴露。好了 &…

vue密码正则验证表单验证_如何在Vue中使用表单验证

vue密码正则验证表单验证介绍 (Introduction) Almost every web application makes use of forms in some way, as such developers always have to tackle form validations. If you are a new developer, it can be hard deciding how best to approach this. Depending on t…