【问题标题】:Check if Flag Was Provided in Go检查 Go 中是否提供了标志
【发布时间】:2016-06-18 23:13:13
【问题描述】:

有了flag包,有没有好办法区分是否传递了字符串flag?

比如flag不传的时候,我想把它设置成一个动态的默认值。但是,如果提供了标志但值为"",我想将其设置为空。

目前我正在做以下事情:

flagHost = flag.String(flagHostFlagKey, "", "...")
...
setHostname := false
for _, arg := range os.Args {
    if arg == "-"+flagHostFlagKey {
        setHostname = true
    }
}

if !setHostname {
     ...

这似乎工作正常,但有点难看。在使用标准标志包的同时有更好的方法吗?

【问题讨论】:

    标签: go command-line-arguments


    【解决方案1】:

    使用flag.Visit()函数:

    func isFlagPassed(name string) bool {
        found := false
        flag.Visit(func(f *flag.Flag) {
            if f.Name == name {
                found = true
            }
        })
        return found
    }
    

    【讨论】:

    • 注意这个需要在flag.Parse之后调用。
    【解决方案2】:

    内置标志类型不支持区分默认值和显式分配给默认值。但是,flag 包非常灵活,允许您使用flag.Value 接口滚动您自己的类型。

    这是一个完整的例子,它包含一个字符串标志,记录它是否被分配。

    package main
    
    import (
        "flag"
        "fmt"
    )
    
    type stringFlag struct {
        set   bool
        value string
    }
    
    func (sf *stringFlag) Set(x string) error {
        sf.value = x
        sf.set = true
        return nil
    }
    
    func (sf *stringFlag) String() string {
        return sf.value
    }
    
    var filename stringFlag
    
    func init() {
        flag.Var(&filename, "filename", "the filename")
    }
    
    func main() {
        flag.Parse()
        if !filename.set {
            fmt.Println("--filename not set")
        } else {
            fmt.Printf("--filename set to %q\n", filename.value)
        }
    }
    

    这是一些运行示例:

    $ go run a.go -filename=abc
    --filename set to "abc"
    
    $ go run a.go -filename=
    --filename set to ""
    
    $ go run a.go
    --filename not set
    

    【讨论】:

    • 在重构代码以实现此功能之前,请注意:如果您将任何 bool 标志包装在 flag.Value 实现中,您将无法在没有参数的情况下在调用时设置它们.换句话说,您过去可以通过简单地使用prog -boolflag progargs 来设置bool 标志,您将不得不改用prog -boolflag true progargs。否则,progargs 将作为-boolflag 的参数被吞并,并且稍后在Args() 中将不可用。
    • 我已经准备好反驳上述评论并批评他们的粗心,然后我意识到这是一年多前的我的评论......无论如何,它不是正确(再吗?)。来自 go 文档,在您的 flag.Value 实现中:If a Value has an IsBoolFlag() bool method returning true, the command-line parser makes -name equivalent to -name=true rather than using the next command-line argument.
    【解决方案3】:

    使用自定义标志类型(此线程中的 stringFlag 示例)的问题是您会稍微扰乱 PrintDefaults 输出(即 --help)。例如,使用字符串用户名标志和字符串标志服务器名标志,--help 看起来像这样:

    -server value
        server:port (default localhost:1234)
    -username string
        username (default "kimmi")
    

    请注意,就用户而言,这两个参数都是字符串参数,但由于 stringFlag 不是字符串,因此呈现方式不同。

    flag 的标志集有一个内部映射,其中包括声明的标志(“正式”)和实际设置的标志(“实际”)。前者可以通过 Lookup() 获得,可惜后者没有公开,或者你可以写:

    var servername = flag.String("server", "localhost:8129", "server:port")
    
    flag.Parse()
    
    if f := flag.CommandLine.LookupActual("server"); f != nil {
        fmt.Printf("server set to %#v\n", f)
    } else {
        fmt.Printf("server not set\n")
    }
    

    如果您想要一致的 PrintDefaults() 输出,似乎您能做的最好的事情就是使用 Visit 来提取您自己的“实际”视图(VisitAll 对“正式”做同样的事情):

    var servername = flag.String("server", "localhost:8129", "server:port")
    
    flag.Parse()
    
    flagset := make(map[string]bool)
    flag.Visit(func(f *flag.Flag) { flagset[f.Name]=true } )
    
    if flagset["server"] {
        fmt.Printf("server set via flags\n")
    } else {
        fmt.Printf("server not explicitly set, using default\n")
    }
    

    【讨论】:

      【解决方案4】:

      要为标志使用动态默认值,请创建默认设置为动态值的标志:

      func main() {
        flagHost = flag.String(flagHostFlagKey, computedHostFlag(), "...")
        flag.Parse()
        // *flagHost equals the return value from computedHostFlag() if 
        // the flag is not specified on the command line.
        ...
      }
      

      使用这种方法,不需要检测是否在命令行上指定了标志。此外,帮助显示正确的默认值。

      如果计算的值取决于其他标志或计算成本很高,则使用其他答案之一中建议的方法。

      【讨论】:

      • 另外,如果computedHostFlag() 是幂等的,这还有一个额外的好处是使用法 (./foo -h) 显示一个准确的默认值。
      • 答案说明帮助显示默认。该函数不需要是幂等的,因为它只被调用一次。
      • 哎呀,完全错过了它已经包含在内。对不起!如果它不是幂等的,那么您在 Usage 中看到的内容可能与随后运行时的值不同。
      • 哦,你是对的。每次执行程序都需要返回相同的值。
      【解决方案5】:

      面临同样的问题,但甚至有布尔标志的复杂情况,在这种情况下,computedHostFlag() 不起作用,因为您只能提供标志创建真或假。 “type stringFlag struct”解决方案也不是最好的,因为破坏了默认值的想法。

      以这种方式解决它:在解析后创建两组具有不同默认值的标志 - 只需检查 - 第一个标志集中的标志与第二个标志集中的标志具有相同的值 - 这意味着标志值由来自命令行的用户。如果它们不同 - 这意味着该标志是默认设置的。

      package main
      
      import (
          "fmt"
          "flag"
      )
      
      func main() {
          args := []string{"-foo="}
      
          flagSet1 := flag.NewFlagSet("flagSet1", flag.ContinueOnError)
          foo1 := flagSet1.String("foo", "-", ``)
          boolFoo1 := flagSet1.Bool("boolfoo", false, ``)
          flagSet1.Parse(args)
      
          flagSet2 := flag.NewFlagSet("flagSet2", flag.ContinueOnError)
          foo2 := flagSet2.String("foo", "+", ``)
          boolFoo2 := flagSet2.Bool("boolfoo", true, ``)
          flagSet2.Parse(args)
      
          if *foo1 != *foo2 {
              fmt.Println("foo flag set by default")
          } else {
              fmt.Println("foo flag provided by user")
          }
      
          if *boolFoo1 != *boolFoo2 {
              fmt.Println("boolfoo flag set by default")
          } else {
              fmt.Println("boolfoo flag provided by user")
          }
      }
      

      playground:https://play.golang.org/p/BVceE_pN5PO,对于真正的 CLI 执行,你可以这样做:https://play.golang.org/p/WNvDaaPj585

      【讨论】:

        【解决方案6】:

        https://stackoverflow.com/a/35809400/3567989 相同,但使用指向字符串而不是自定义结构的指针。 *string 如果未设置则为 nil,如果设置则为非 nil。

        package main
        
        import (
            "flag"
            "fmt"
        )
        
        type stringPtrFlag struct {
            ptr **string
        }
        
        func (f stringPtrFlag) String() string {
            if *f.ptr == nil {
                return ""
            }
            return **f.ptr
        }
        
        func (f stringPtrFlag) Set(s string) error {
            *f.ptr = &s
            return nil
        }
        
        var filename *string
        
        func init() {
            flag.Var(stringPtrFlag{&filename}, "filename", "the filename")
        }
        
        func main() {
            flag.Parse()
            if filename == nil {
                fmt.Println("--filename not set")
            } else {
                fmt.Printf("--filename set to %q\n", *filename)
            }
        }
        

        【讨论】:

          【解决方案7】:

          我觉得比较靠谱的方法是检查命令行参数(os.Args[1:])中是否有flag前缀为“prefix”+str,所以函数:

          func isInSlice(str string, list []string, prefix string) bool {
              for _, v := range list {
                  if strings.HasPrefix(v, prefix + str) {
                      return true
                  }
              }
              return false
          }
          

          【讨论】:

            【解决方案8】:

            我发现我们有 Lookup() 方法:

            func isFlagPassed(name string) bool {
              rs := flag.Lookup(name)
              return rs != nil 
            }
            

            完整的docs

            【讨论】:

            • flag.Lookup() 返回命名标志的标志结构。它不会告诉您 CLI 中是否实际提供了该标志。
            【解决方案9】:

            FlagSet在我的环境(go version go1.13.4 windows/amd64)中没有函数LookupActual(),内部映射actual中提到Ben L's answer不能直接访问。

            我有一种方法可以检查是否使用reflect 设置了标志:

            import "reflect"
            
            fs := flag.NewFlagSet("the flags", flag.ExitOnError)
            flag_name := "host"
            host := fs.String(flag_name, "localhost", "specify the host address")
            // other flags
            fs.Parse(os.Args[1:])
            
            if reflect.Indirect(reflect.ValueOf(fs)).FieldByName("actual").MapIndex(reflect.ValueOf(flag_name)).IsValid() {
                 fmt.Printf("the host flag is set with value %v", *host)
            } else {
                 fmt.Printf("the host flag is not set")
            }
            

            【讨论】:

            • 这是访问未导出的字段。 Go 不保证私有 API 的稳定性。程序不应该这样做,这可能会在任何 Go 版本中中断。
            猜你喜欢
            • 2016-07-05
            • 2020-10-09
            • 2011-03-09
            • 1970-01-01
            • 2015-12-29
            • 1970-01-01
            • 1970-01-01
            • 2018-03-25
            • 1970-01-01
            相关资源
            最近更新 更多