程序员社区

Go:flag.Func自定义命令行参数

在最近的Go 1.16版本中,我最喜欢的一个特性是在flag包中增加了一个很受欢迎的功能:flag. func()函数。使得在应用程序中解析自定义命令行参数变得更加容易。

例如,假设你想将--pause=10s这个命令行参数解析为time.Duration类型,或者将--urls="http://example.com http://example.org"直接解析到[]string切片中,如果在以前你有两种选择。你可以创建一个自定义类型来实现flag.Value接口,或者使用第三方包例如pflag。

但现在flag.Func()函数提供了一个非常简单轻量的选择。在本文中,我们通过几个例子来看看怎么在代码中使用这个函数。

解析自定义参数类型

为了演示它是如何工作的,让我们从上面给出的两个示例开始,并创建一个示例程序,该程序接受一个url列表,然后在它们之间进行暂停一段时间并打印它们。类似于:

$ go run .  --pause=3s --urls="http://example.com http://example.org http://example.net"
2021/03/08 08:16:04 http://example.com
2021/03/08 08:16:07 http://example.org
2021/03/08 08:16:10 http://example.net

要做到这一点,我们需要做两件事:

  • 将--pause的值从人为可读的字符串例如200ms、5s或者10m转为Go的time.Duration类型。我们可以使用time.ParseDuration()函数实现。
  • 将--urls参数转换为字符串切片,这样就可以循环遍历。strings.Field函数非常适合实现这个任务。
    我们结合flag.Func()使用如下:
package main

import (
    "flag"
    "log"
    "strings"
    "time"
)

func main() {
    // 首先需要定义所需类型来存放命令参数,需要设定默认值,如果
    // 命令行参数未提供对应值,运行时可使用默认值
    var (
        urls  []string                    //切片变量定义,默认为空
        pause time.Duration = time.Second //暂停时间默认1s
    )

    // flag.Func函数需要三个参数: 命令行参数名称, 参数描述和一个
    // func(string) error匿名函数,在程序运行时调用这个函数处理命令行参数
    // 并将其赋值给需要的变量。在这里例子中,我们用strings.Fields()函数
    // 根据空格将字符串切分并将结果赋值给定义的urls变量,函数返回nil
    // 表示解析无任何错误。
    flag.Func("urls", "List of URLs to print", func(flagValue string) error {
        urls = strings.Fields(flagValue)
        return nil
    })

    // 同样,我们可以做同样的事情来解析暂停时间。time.ParseDuration()
    // 函数可能会在这里抛出一个错误,因此我们要确保从函数中返回错误。
    flag.Func("pause", "Duration to pause between printing URLs", func(flagValue string) error {
        var err error
        pause, err = time.ParseDuration(flagValue)
        return err
    })

    // 重要的是,调用flag.Parse()来触发参数的解析。
    flag.Parse()

    // 输出urls,在每次迭代之间暂停。
    for _, u := range urls {
        log.Println(u)
        time.Sleep(pause)
    }
}

如果您尝试运行这个程序,您应该会发现参数被解析,并且工作方式与您期望的一样。例如:

$ go run . --pause=500ms --urls="http://example.com http://example.org http://example.net"
2021/03/08 08:22:33 http://example.com
2021/03/08 08:22:34 http://example.org
2021/03/08 08:22:34 http://example.net

然而,如果您提供了一个无效的命令行参数,在一个flag. func()函数中报错,Go将自动显示相应的错误信息并退出。例如:

$ go run . --pause=500xx --urls="http://example.com http://example.org http://example.net"
invalid value "500xx" for flag -pause: time: unknown unit "xx" in duration "500xx"
Usage of /tmp/go-build3141872390/b001/exe/example.text:
  -pause value
        Duration to pause between printing URLs
  -urls value
        List of URLs to print
exit status 2

需要注意的是如果某个命令行参数没有提供,相应的flag.Func()函数将不会执行。这意味着不能在flag.Func()中对相应变量设定默认值,因此下面的代码不会生效:

flag.Func("pause", "Duration to pause between printing URLs (default 1s)", func(flagValue string) error {
    // 不要这样做!如果参数值为""则不会调用此函数.
    if flagValue == "" {
        pause = time.Second
        return nil
    }

    var err error
    pause, err = time.ParseDuration(flagValue)
    return err
})

不过,从好的方面来说,flag.Func()函数中所包含的代码没有限制,所以如果你愿意,可以用它来做更多的事情,可将urls解析为[]*url.URL切片而不是[]string。如下所示:

var (
    urls  []*url.URL                 
    pause time.Duration = time.Second
)

flag.Func("urls", "List of URLs to print", func(flagValue string) error {
    for _, u := range strings.Fields(flagValue) {
        parsedURL, err := url.Parse(u)
        if err != nil {
            return err
        }
        urls = append(urls, parsedURL)
    }
    return nil
})

校验命令行参数值

flag.Func()函数提供了校验命令行参数内容的机会。例如,我们的应用程序有一个environment参数,你需要限制该参数只能赋值为development、staging和production三个值。
要实现这个功能,你可以实现类似下面flag.Func()函数

package main

import (
    "errors"
    "flag"
    "fmt"
)

func main() {
    var (
        environment string = "development"
    )

    flag.Func("environment", "Operating environment", func(flagValue string) error {
        for _, allowedValue := range []string{"development", "staging", "production"} {
            if flagValue == allowedValue {
                environment = flagValue
                return nil
            }
        }
        return errors.New(`must be one of "development", "staging" or "production"`)
    })

    flag.Parse()

    fmt.Printf("The operating environment is: %s\n", environment)
}

使用flag.Func创建帮助函数

如果您发现自己在flag.Func()函数中重复相同的代码,或者逻辑变得过于复杂,可以将其分解为一个可重用的帮助函数。例如,我们可以重写上面的例子,通过泛型enumFlag()函数来处理--environment参数,如下所示:

package main

import (
    "flag"
    "fmt"
)

func main() {
    var (
        environment string = "development"
    )

    enumFlag(&environment, "environment", []string{"development", "staging", "production"}, "Operating environment")

    flag.Parse()

    fmt.Printf("The operating environment is: %s\n", environment)
}

func enumFlag(target *string, name string, safelist []string, usage string) {
    flag.Func(name, usage, func(flagValue string) error {
        for _, allowedValue := range safelist {
            if flagValue == allowedValue {
                *target = flagValue
                return nil
            }
        }

        return fmt.Errorf("must be one of %v", safelist)
    })
}
赞(0) 打赏
未经允许不得转载:IDEA激活码 » Go:flag.Func自定义命令行参数

一个分享Java & Python知识的社区