69

Is there a way how to set that certain flags are mandatory, or do I have to check for their presence on my own?

Petr
  • 62,528
  • 13
  • 153
  • 317

9 Answers9

58

The flag package does not support mandatory or required flags (meaning the flag must be specified explicitly).

What you can do is use sensible default values for (all) flags. And if a flag is something like there is no sensible default, check the value at the start of your application and halt with an error message. You should do flag value validation anyway (not just for required flags), so this shouldn't mean any (big) overhead, and this is a good practice in general.

icza
  • 389,944
  • 63
  • 907
  • 827
20

As already mentioned, the flag package does not provide this feature directly and usually you can (and should) be able to provide a sensible default. For cases where you only need a small number of explicit arguments (e.g. an input and output filename) you could use positional arguments (e.g. after flag.Parse() check that flag.NArg()==2 and then input, output := flag.Arg(0), flag.Arg(1)).

If however, you have a case where this isn't sensible; say a few integer flags you want to accept in any order, where any integer value is reasonable, but no default is. Then you can use the flag.Visit function to check if the flags you care about were explicitly set or not. I think this is the only way to tell if a flag was explicitly set to it's default value (not counting a custom flag.Value type with a Set implementation that kept state).

For example, perhaps something like:

    required := []string{"b", "s"}
    flag.Parse()

    seen := make(map[string]bool)
    flag.Visit(func(f *flag.Flag) { seen[f.Name] = true })
    for _, req := range required {
        if !seen[req] {
            // or possibly use `log.Fatalf` instead of:
            fmt.Fprintf(os.Stderr, "missing required -%s argument/flag\n", req)
            os.Exit(2) // the same exit code flag.Parse uses
        }
    }

Playground

This would produce an error if either the "-b" or "-s" flag was not explicitly set.

Community
  • 1
  • 1
Dave C
  • 7,729
  • 4
  • 49
  • 65
11

go-flags lets you declare both required flags and required positional arguments:

var opts struct {
    Flag string `short:"f" required:"true" name:"a flag"`
    Args struct {
        First   string `positional-arg-name:"first arg"`
        Sencond string `positional-arg-name:"second arg"`
    } `positional-args:"true" required:"2"`
}
args, err := flags.Parse(&opts)
iKanor
  • 513
  • 5
  • 7
10

I like github.com/jessevdk/go-flags package to use in CLI. It provides a required attribute, to set a mandatory flag:

var opts struct {
...
    // Example of a required flag
    Name string `short:"n" long:"name" description:"A name" required:"true"`
...
}
030
  • 10,842
  • 12
  • 78
  • 123
RoninDev
  • 5,446
  • 3
  • 23
  • 37
8

If you have flag path, simply check if *path contains some value

var path = flag.String("f", "", "/path/to/access.log")
flag.Parse()
if *path == "" {
    usage()
    os.Exit(1)
}
Fedir RYKHTIK
  • 9,844
  • 6
  • 58
  • 68
ivan73
  • 695
  • 1
  • 9
  • 16
6

I agree with this solution but, in my case default values are usually environment values. For example,

dsn := flag.String("dsn", os.Getenv("MYSQL_DSN"), "data source name")

And in this case, I want to check if the values are set from invocation (usually local development) or environment var (prod environment).

So with some minor modifications, it worked for my case.

Using flag.VisitAll to check the value of all flags.

required := []string{"b", "s"}
flag.Parse()

seen := make(map[string]bool)
flag.VisitAll(func(f *flag.Flag) {
    if f.Value.String() != "" {
        seen[f.Name] = true
    }
})
for _, req := range required {
    if !seen[req] {
        // or possibly use `log.Fatalf` instead of:
        fmt.Fprintf(os.Stderr, "missing required -%s argument/flag\n", req)
        os.Exit(2) // the same exit code flag.Parse uses
    }
}

Test example in plauground

canhizares
  • 71
  • 1
  • 4
  • `seen` no longer means "seen" in your code but instead "empty string", at which point you'd be much better off just checking that `flag.Lookup(req).Value.String() != ""` and ditching the map entirely. You also may be better off by getting values from the environment in a different way rather than changing flag defaults (e.g. perhaps something like [`envflag`](https://godoc.org/bitbucket.org/dchapes/envflag)). – Dave C Aug 06 '19 at 13:42
  • I agree with the name but I wanted to keep the same as the previous solution because it was just an example of an adaptation to a solution that is not mine. On using another way, I still prefer to use [flag](https://golang.org/pkg/flag/) because it allows me to pass parameters by command line in a development environment and leave the environment variables for production. – canhizares Aug 07 '19 at 22:28
  • Just for completeness (I don't expect anyone uses it), the above linked `envflag` package moved to [`envflag`](https://pkg.go.dev/hg.sr.ht/~dchapes/envflag.hg) since bitbucket deleted all non-git repositories :(. – Dave C Apr 16 '21 at 13:08
1

Here's a full working example.

Work around the Usage and DefValue attributes, and wrap your flags into a struct.

package main

import (
    "flag"
    "fmt"
    "os"
)

type CliFlag struct {
    Required bool
    Usage    string
    Name     string
    Address  *string
}

func init() {
    flags := [...]CliFlag{
        {
            Required: true,
            Usage:    "(github.com) repository URI",
            Name:     "repo-uri",
            Address:  nil,
        },
        {
            Required: true,
            Usage:    "(Zombro) repository workspace",
            Name:     "repo-workspace",
            Address:  nil,
        },
        {
            Required: true,
            Usage:    "(zzio) repository slug",
            Name:     "repo-slug",
            Address:  nil,
        },
    }
    for i, f := range flags {
        f.Address = flag.String(f.Name, "", f.Usage)
        flags[i] = f
    }
    flag.Parse()
    missing := make([]string, 0)
    for _, f := range flags {
        if *f.Address == "" && f.Required {
            missing = append(missing, f.Name)
        }
    }
    if len(missing) > 0 {
        fmt.Printf("missing required flags: %v \n", missing)
        flag.Usage()
        os.Exit(1)
    }
}

func main() {
    fmt.Println("main")
}

missing

$ go run . -repo-slug test                  
missing required flags: [repo-uri repo-workspace] 
Usage of /var/folders/mg/86n5kszs27bdqj0fpswvr0m00000gn/T/go-build2061541798/b001/exe/zzio:
  -repo-slug string
        (zzio) repository slug
  -repo-uri string
        (github.com) repository URI
  -repo-workspace string
        (Zombro) repository workspace
exit status 1
Zombro
  • 311
  • 1
  • 9
0

A thing to note is that when you do:

password := flag.String("password", "", "the password")
flag.Parse()

The default is set by flag.String, not flag.Parse.

So if you instead do:

const unspecified = "\x00"
password := flag.String("password", "", "the password")
*password = unspecified
flag.Parse()

Then *password == unspecified if you don't specify it explicitly in the command line. This is my go to for strings when I want to distinguish "empty" from "unspecified".

Nuno Cruces
  • 1,584
  • 15
  • 18
-1

Or you could docopt, where you only have to write the "usage" text. Docopt interprets the usage text and creates an argument map. This opens up a whole lot of possibilities, all following the POSIX usage text standard. This library is available for about 20 languages already.

https://github.com/docopt/docopt.go

package main

import (
        "fmt"
        "github.com/docopt/docopt-go"
)

const (
        Usage = `Naval Fate.

Usage:
  naval_fate ship new <name>...
  naval_fate ship <name> move <x> <y> [--speed=<kn>]
  naval_fate ship shoot <x> <y>
  naval_fate mine (set|remove) <x> <y> [--moored|--drifting]
  naval_fate -h | --help
  naval_fate --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --speed=<kn>  Speed in knots [default: 10].
  --moored      Moored (anchored) mine.
  --drifting    Drifting mine.`
)

func main() {
        args, _ := docopt.ParseDoc(Usage)
        fmt.Println(args)
}
Gerben
  • 122
  • 4