45

With the flag package, is there a good way to distinguish if a string flag was passed?

For example, when the flag is not passed, I want to set it to a dynamic default value. However, I want to set it to empty if the flag was provided but with a value of "".

Current I am doing the following:

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

if !setHostname {
     ...

Which seems to work fine, but is kind of ugly. Is there a better way while staying with the standard flag package?

Kyle Brandt
  • 26,938
  • 37
  • 124
  • 165

9 Answers9

52

Use the flag.Visit()

Description: Visit visits the command-line flags in lexicographical order, calling fn for each. It visits only those flags that have been set.

use:

func isFlagPassed(name string) bool {
    found := false
    flag.Visit(func(f *flag.Flag) {
        if f.Name == name {
            found = true
        }
    })
    return found
}
b01
  • 4,076
  • 2
  • 30
  • 30
Markus Heukelom
  • 636
  • 6
  • 2
  • 6
    Note that this needs to be called after `flag.Parse`. – ec-m Aug 21 '20 at 11:01
  • 1
    `flag.Visit` is just what I needed. But I overlooked it many times because the name of the function does not indicate that it only visit flag that have been set. Wasted time because I did not read the whole function description (sad panda) – b01 Aug 28 '22 at 17:51
  • Thanks, that's an easy and easy to overlook solution to check if a zero value was passed. also understandable for beginners. – jagottsicher Sep 16 '22 at 01:18
30

The built-in flag types don't support distinguishing default values and explicit assignment to the default value. However, the flag package is quite flexible, and allows you to roll your own type that does, using the flag.Value interface.

Here's a full example that contains a string flag which records if it's been assigned to.

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)
    }
}

Here's some example runs:

$ 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
Paul Hankin
  • 54,811
  • 11
  • 92
  • 118
  • 2
    a word of warning before you go refactoring your code to implement this: if you wrap any `bool` flags in a `flag.Value` implementation, you won't be able to set them at invocation without argument. in other words, where you used to be able to set `bool` flags by simply using `prog -boolflag progargs`, you'll have to instead use `prog -boolflag true progargs`. otherwise, `progargs` gets swallowed up as the argument to `-boolflag` and won't be available later in `Args()`. – ardnew Oct 27 '18 at 02:20
  • 4
    I was so ready to counter the above comment and criticize their carelessness, and then I realized it was *my* comment from over a year ago... anyway, it is not correct (anymore?). From the go docs, in your `flag.Value` implementation: `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.` – ardnew Jan 28 '20 at 00:36
18

The issue with using a custom flag type (the stringFlag example in this thread) is you'll slightly upset the PrintDefaults output (i.e. --help). For example, with a string username flag and a stringFlag servername flag, --help looks like this:

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

Note these are both string arguments as far as the user is concerned, but presented differently as a stringFlag is not a string.

flag's Flagset has an internal map that includes the flags that were declared ('formal') and those actually set ('actual'). The former is available via Lookup(), though alas the latter is not exposed, or you could just write:

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")
}

Seems like the best you can do, if you want consistent PrintDefaults() output, is to use Visit to extract your own view of 'actual' (VisitAll does the same thing with 'formal'):

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")
}
Ben L
  • 192
  • 1
  • 7
3

To use a dynamic default value for a flag, create the flag with the default set to the dynamic value:

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.
  ...
}

With this approach, it's not necessary to detect if the flag is specified on the command line. Also, help shows the correct default.

If the computed value depends on other flags or is expensive to calculate, then use the approach suggested in one of the other answers.

Charlie Tumahai
  • 113,709
  • 12
  • 249
  • 242
  • Plus, if `computedHostFlag()` is idempotent, this has the added benefit of making the usage (`./foo -h`) display an accurate default value. – Brian Mar 05 '16 at 20:27
  • The answer notes that the help shows the default. The function need not be idempotent as it's only called once. – Charlie Tumahai Mar 05 '16 at 20:48
  • Whoops, completely missed that it was already included. Sorry! If it's not idempotent though, what you see with the Usage may differ than what the value is when it is subsequently run. – Brian Mar 05 '16 at 20:48
  • Oh, you are right. It need to return same value each time program is executed. – Charlie Tumahai Mar 05 '16 at 20:53
1

Face with same problem, but have even complex case with bool flag, in this case computedHostFlag() not working, since you can provide to flag creation only true or false. "type stringFlag struct" solution also not the best, since ruin idea of default values.

Solve it in this way: create two sets of flags, with different default values, after parse - just check - if flag in first flagset have the same value that flag from second flagset - that it means that flag value was provided by user from command line. If they different - than this mean that flag was set by default.

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 , for real CLI execution, you can do something like that: https://play.golang.org/p/WNvDaaPj585

ewgra
  • 141
  • 2
  • 4
1

Same as https://stackoverflow.com/a/35809400/3567989 but with a pointer to a string instead of a custom struct. The *string is nil if unset, non-nil if set.

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)
    }
}
emersion
  • 393
  • 4
  • 10
0

I think a more reliable way is to check whether any flag in the command-line parameters (os.Args[1:]) is prefixed by "prefix" + str, so the function:

func isInSlice(str string, list []string, prefix string) bool {
    for _, v := range list {
        if strings.HasPrefix(v, prefix + str) {
            return true
        }
    }
    return false
}
karel
  • 5,489
  • 46
  • 45
  • 50
zhayu
  • 1
-1

I found that we have the Lookup() method:

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

Full docs

taynguyen
  • 2,961
  • 1
  • 26
  • 26
-1

The FlagSet does not have a function LookupActual() in my environment (go version go1.13.4 windows/amd64), and the internal map actual mentioned in Ben L's answer can not be accessed directly.

I have an approach to check if a flag is set using 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")
}
  • This is accessing unexported fields. Go doesn't guarantee stability for private APIs. Programs shouldn't do this, this can break at any Go release. – emersion May 11 '20 at 15:19