Ive
Ive

Reputation: 413

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])
...

Upvotes: 20

Views: 17000

Answers (4)

Erik Rothoff
Erik Rothoff

Reputation: 5123

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.

Upvotes: 4

Alberto Bregliano
Alberto Bregliano

Reputation: 11

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
    }

Upvotes: 1

notzippy
notzippy

Reputation: 498

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:])

Upvotes: 19

justinas
justinas

Reputation: 6857

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

Upvotes: 29

Related Questions