Issue with golang flags - go

So recently I ran into a problem with Golang flags and was curious to know if this is as intended, if its a problem or if I'm being a blithering idiot and there is a very simple answer to this.
So using the following code:
func main() {
test := flag.String("-test", "", "test var")
flag.Parse()
if *test != "" {
fmt.Println(*test)
}
}
And then run it using the following command ./main -test 1
You get the following autogenerated error:
flag provided but not defined: -test
Usage of ./main:
--test string
test var
The same happens if you then use ./main --test 1, you get the same error. The only way I have found around this is to change the flag definition to be test := flag.String("test", "", "test var") and then run it with ./main -test 1.
So my question is why can you not use double hyphens with flags? If you can, where did I go wrong when doing this?

You can use double hyphens and this is documented in the package here:
One or two minus signs may be used; they are equivalent.
Having declared your flags, flag.Parse() will try to remove one or two hyphens from the flags passed and use the remaining characters of each flag to detect whether one has been declared or not. This is done internally by the flag package in parseOne().
Declaring a flag with name -test will literally map it as is, resulting in flag.Parse() internally looking for the name test instead of -test which is the one you actually declared, resulting in the error you see.
Instead, use only the name of the flag when declaring one:
test := flag.String("test", "", "test var")
and you can use this flag with both one (./main -test 1) or two hyphens (./main --test 2).

Try to define the flag without -:
func main() {
test := flag.String("test", "", "test var")
flag.Parse()
if *test != "" {
fmt.Println(*test)
}
}

For us the flag package is probably not the nicest thing to use here.
Instead of that, you might want to try getopt package owned by Google, which provides the more standard command line parsing (vs the flag package).
package main
import (
"fmt"
"os"
"github.com/pborman/getopt"
)
func main() {
optName := getopt.StringLong("name", 'n', "", "Your name")
optHelp := getopt.BoolLong("test", 0, "test var")
getopt.Parse()
if *optHelp {
getopt.Usage()
os.Exit(0)
}
fmt.Println("Hello " + *optName + "!")
}
$ ./hello --help
Usage: hello [--test] [-n value] [parameters ...]
--test test var
-n, --name=value Your name
$ ./hello --name Bob
Hello Bob!

Related

How do I iterate through command line arguments and collect what's left over after the flags?

My goal is for "init", "init -site=test", both versions of init and also the standalone "debug" command to be accepted at the command line, and to treat anything left over as a filename.
What actually happens is that in the case of "init -site=test" for some reason the "-site=test" is also accepted as a filename. How can I stop that from happening?
package main
import (
"flag"
"fmt"
"os"
)
func main() {
initCmd := flag.NewFlagSet("init", flag.ExitOnError)
initSiteName := initCmd.String("site", "", "Main name for your site")
flag.Parse()
for pos, cmd := range os.Args {
switch cmd {
case "debug":
fmt.Printf("debug\n")
case "init":
initCmd.Parse(os.Args[pos+1:])
fmt.Printf("init\n site name:%v\n", *initSiteName)
default:
fmt.Printf("Filename: %v\n", cmd);
}
}
}
It's not very convenient using the flag package. From the doc:
Flag parsing stops just before the first non-flag argument ("-" is a non-flag argument) or after the terminator "--".
You would have to do it manually:
After parsing, the arguments following the flags are available as the slice flag.Args() or individually as flag.Arg(i).
Or you can use another package.

How to pass a flag to a command in go lang?

I have been trying to run a command and parse the output in golang. Here is a sample of what I am trying to do:
package main
import (
"fmt"
"os/exec"
)
func main() {
out,err := exec.Command("ls -ltr").Output()
if err != nil {
fmt.Println("Error: %s", err)
}
fmt.Printf("%s",out)
}
Now, when I am trying to run "ls -ltr", I get this error:
Error: %s exec: "ls -ltr": executable file not found in $PATH
So, basically go is looking for whole "ls -ltr" in PATH. And it's not there obviously. Is there any way I can pass a flag to any argument?TIA.
You pass arguments to the program by passing more arguments to the function - it's variadic:
out,err := exec.Command("ls","-ltr").Output()
https://golang.org/pkg/os/exec/#Command
This is a pretty common convention with exec-style functions which you will see in most languages. The other common pattern is builders.
Sometimes the layout of arguments you need to pass won't be known at compile-time (though it's not a good idea to send arbitrary commands to the system - stay safe!). If you want to pass an unknown number of arguments, you can use an array with some special syntax:
// Populate myArguments however you like
myArguments := []string{"bar","baz"}
// Pass myArguments with "..." to use variadic behaviour
out,err := exec.Command("foo", myArguments...).Output()

Can command line flags in Go be set to mandatory?

Is there a way how to set that certain flags are mandatory, or do I have to check for their presence on my own?
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.
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.
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)
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"`
...
}
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)
}
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
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)
}
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".
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

Golang Flag gets interpreted as first os.Args argument

I would like to run my program like this:
go run launch.go http://example.com --m=2 --strat=par
"http://example.com" gets interpreted as the first command line argument, which is ok, but the flags are not parsed after that and stay at the default value. If I put it like this:
go run launch.go --m=2 --strat=par http://example.com
then "--m=2" is interpreted as the first argument (which should be the URL).
I could also just remove the os.Args completely, but then I would have only optional flags and I want one (the URL) to be mandatory.
Here's my code:
package main
import (
"fmt"
"webcrawler/crawler"
"webcrawler/model"
"webcrawler/urlutils"
"os"
"flag"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("Url must be provided as first argument")
}
strategy := flag.String("strat", "par", "par for parallel OR seq for sequential crawling strategy")
routineMultiplier := flag.Int("m", 1, "Goroutine multiplier. Default 1x logical CPUs. Only works in parallel strategy")
page := model.NewBasePage(os.Args[1])
urlutils.BASE_URL = os.Args[1]
flag.Parse()
pages := crawler.Crawl(&page, *strategy, *routineMultiplier)
fmt.Printf("Crawled: %d\n", len(pages))
}
I am pretty sure that this should be possible, but I can't figure out how.
EDIT:
Thanks justinas for the hint with the flag.Args(). I now adapted it like this and it works:
...
flag.Parse()
args := flag.Args()
if len(args) != 1 {
log.Fatal("Only one argument (URL) allowed.")
}
page := model.NewBasePage(args[0])
...
os.Args doesn't really know anything about the flag package and contains all command-line arguments. Try flag.Args() (after calling flag.Parse(), of course).
As a followup, to parse flags that follow a command like
runme init -m thisis
You can create your own flagset to skip the first value like
var myValue string
mySet := flag.NewFlagSet("",flag.ExitOnError)
mySet.StringVar(&myValue,"m","mmmmm","something")
mySet.Parse(os.Args[2:])
This tripped me up too, and since I call flag.String/flag.Int64/etc in a couple of places in my app, I didn't want to have to pass around a new flag.FlagSet all over the place.
// If a commandline app works like this: ./app subcommand -flag -flag2
// `flag.Parse` won't parse anything after `subcommand`.
// To still be able to use `flag.String/flag.Int64` etc without creating
// a new `flag.FlagSet`, we need this hack to find the first arg that has a dash
// so we know when to start parsing
firstArgWithDash := 1
for i := 1; i < len(os.Args); i++ {
firstArgWithDash = i
if len(os.Args[i]) > 0 && os.Args[i][0] == '-' {
break
}
}
flag.CommandLine.Parse(os.Args[firstArgWithDash:])
The reason I went with this is because flag.Parse just calls flag.CommandLine.Parse(os.Args[1:]) under the hood anyway.
You can check if the Arg starts with "--" or "-" and avoid using that Arg in a loop.
For example:
for _, file := range os.Args[1:] {
if strings.HasPrefix(file, "--") {
continue
}
//do stuff
}

Explanation of Flags in Go

Can anyone explain flags in Go?
flag.Parse()
var omitNewline = flag.Bool("n", false, "don't print final newline")
flags are a common way to specify options for command-line programs.
package main
import (
"flag"
"fmt"
)
var (
env *string
port *int
)
// Basic flag declarations are available for string, integer, and boolean options.
func init() {
env = flag.String("env", "development", "a string")
port = flag.Int("port", 3000, "an int")
}
func main() {
// Once all flags are declared, call flag.Parse() to execute the command-line parsing.
flag.Parse()
// Here we’ll just dump out the parsed options and any trailing positional
// arguments. Note that we need to dereference the points with e.g. *evn to
// get the actual option values.
fmt.Println("env:", *env)
fmt.Println("port:", *port)
}
Run Programs:
go run main.go
Try out the run program by first giving it without flags. Note that if you omit flags they automatically take their default values.
go run command-line-flags.go --env production --port 2000
If you provide a flag with specified value then default will overwrite by passed one.
See http://golang.org/pkg/flag/ for a full description.
The arguments for flag.Bool are (name string, value bool, usage string)
name is the argument to look for, value is the default value and
usage describes the flag's purpose for a -help argument or similar, and is displayed with flag.Usage().
For more detailed example check here
flag is used to parse command line arguments. If you pass "-n" as a command line argument, omitNewLine will be set to true. It's explained a bit farther in the tutorial :
Having imported the flag package, line 12 creates a global variable to hold the value of echo's -n flag. The variable omitNewline has type *bool, pointer to bool.
Personally, I prefer the Var type functions, as they take a reference, rather
than returning a reference. That way you can use the variable without
dereferencing:
package main
import "flag"
func main() {
var omitNewline bool
flag.BoolVar(&omitNewline, "n", false, "don't print final newline")
flag.Parse()
println(omitNewline)
}
https://golang.org/pkg/flag#BoolVar

Resources