DEV Community

Cover image for Command-Line Arguments in Go: How to Use the Flag Library
Leapcell
Leapcell

Posted on

2 1 1 1 1

Command-Line Arguments in Go: How to Use the Flag Library

Image description

Leapcell: The Next-Gen Serverless Platform for Web Hosting

Introduction

flag is used to parse command-line options. People with experience using Unix-like systems should be familiar with command-line options. For example, the command ls -al lists detailed information about all files and directories in the current directory, where -al is the command-line option.

Command-line options are commonly used in actual development, especially when writing tools.

Specify the path of the configuration file. For example, postgres -D /usr/local/pgsql/data starts the PostgreSQL server with the specified data directory;
Customize certain parameters. For example, python -m SimpleHTTPServer 8080 starts an HTTP server listening on port 8080. If not specified, it will listen on port 8000 by default.

Quick Start

The first step in learning a library is of course to use it. Let's first take a look at the basic usage of the flag library:

package main

import (
  "fmt"
  "flag"
)

var (
  intflag int
  boolflag bool
  stringflag string
)

func init() {
  flag.IntVar(&intflag, "intflag", 0, "int flag value")
  flag.BoolVar(&boolflag, "boolflag", false, "bool flag value")
  flag.StringVar(&stringflag, "stringflag", "default", "string flag value")
}

func main() {
  flag.Parse()

  fmt.Println("int flag:", intflag)
  fmt.Println("bool flag:", boolflag)
  fmt.Println("string flag:", stringflag)
}
Enter fullscreen mode Exit fullscreen mode

You can first compile the program and then run it (I'm using macOS):

$ go build -o main main.go
$ ./main -intflag 12 -boolflag 1 -stringflag test
Enter fullscreen mode Exit fullscreen mode

Output:

int flag: 12
bool flag: true
string flag: test
Enter fullscreen mode Exit fullscreen mode

If you don't set a certain option, the corresponding variable will take the default value:

$ ./main -intflag 12 -boolflag 1
Enter fullscreen mode Exit fullscreen mode

Output:

int flag: 12
bool flag: true
string flag: default
Enter fullscreen mode Exit fullscreen mode

You can see that the option stringflag that was not set has the default value default.

You can also directly use go run. This command will first compile the program to generate an executable file and then execute the file, passing other options in the command line to this program.

$ go run main.go -intflag 12 -boolflag 1
Enter fullscreen mode Exit fullscreen mode

You can use -h to display the option help information:

$ ./main -h
Usage of /path/to/main:
  -boolflag
        bool flag value
  -intflag int
        int flag value
  -stringflag string
        string flag value (default "default")
Enter fullscreen mode Exit fullscreen mode

To summarize, the general steps to use the flag library:

  1. Define some global variables to store the values of the options, such as intflag, boolflag, and stringflag here;
  2. Use the flag.TypeVar method in the init method to define the options. Here, Type can be a basic type like Int, Uint, Float64, Bool, or it can also be a time interval time.Duration. When defining, pass in the address of the variable, the option name, the default value, and the help information;
  3. Call flag.Parse in the main method to parse the options from os.Args[1:]. Since os.Args[0] is the path of the executable program, it will be excluded.

Points to Note

The flag.Parse method must be called after all options are defined, and no new options can be defined after flag.Parse is called. If you follow the previous steps, there will basically be no problems.

Since init is executed before all the code, by putting all the option definitions in init, all options will already be defined when flag.Parse is executed in the main function.

Option Format

The flag library supports three command-line option formats.

-flag
-flag=x
-flag x
Enter fullscreen mode Exit fullscreen mode

Both - and -- can be used, and they have the same function. Some libraries use - to represent short options and -- to represent long options. Relatively speaking, flag is easier to use.

The first form only supports boolean options. If it appears, it is true, and if it does not appear, it takes the default value.

The third form does not support boolean options. Because boolean options in this form may exhibit unexpected behavior in Unix-like systems. Consider the following command:

cmd -x *
Enter fullscreen mode Exit fullscreen mode

Here, * is a shell wildcard. If there are files named 0 or false, the boolean option -x will take the value false. Otherwise, the boolean option -x will take the value true. And this option consumes one argument.

If you want to explicitly set a boolean option to false, you can only use the form -flag=false.

Parsing stops when the first non-option argument (that is, an argument that does not start with - or --) or the terminator -- is encountered. Run the following program:

$ ./main noflag -intflag 12
Enter fullscreen mode Exit fullscreen mode

The output will be:

int flag: 0
bool flag: false
string flag: default
Enter fullscreen mode Exit fullscreen mode

Because the parsing stops when it encounters noflag, and the subsequent option -intflag is not parsed. So all options take their default values.

Run the following program:

$ ./main -intflag 12 -- -boolflag=true
Enter fullscreen mode Exit fullscreen mode

The output will be:

int flag: 12
bool flag: false
string flag: default
Enter fullscreen mode Exit fullscreen mode

First, the option intflag is parsed and its value is set to 12. After encountering --, the parsing stops, and the subsequent --boolflag=true is not parsed, so the boolflag option takes the default value false.

After the parsing stops, if there are still command-line arguments, the flag library will store them, and you can get a slice of these arguments through the flag.Args method.

You can get the number of unparsed arguments through the flag.NArg method, and access the argument at position i (starting from 0) through flag.Arg(i).

The number of options can also be obtained by calling the flag.NFlag method.

Slightly modify the above program:

func main() {
  flag.Parse()

  fmt.Println(flag.Args())
  fmt.Println("Non-Flag Argument Count:", flag.NArg())
  for i := 0; i < flag.NArg(); i++ {
    fmt.Printf("Argument %d: %s\n", i, flag.Arg(i))
  }

  fmt.Println("Flag Count:", flag.NFlag())
}
Enter fullscreen mode Exit fullscreen mode

Compile and run this program:

$ go build -o main main.go
$ ./main -intflag 12 -- -stringflag test
Enter fullscreen mode Exit fullscreen mode

Output:

[-stringflag test]
Non-Flag Argument Count: 2
Argument 0: -stringflag
Argument 1: test
Enter fullscreen mode Exit fullscreen mode

After the parsing stops when it encounters --, the remaining arguments -stringflag test are saved in flag, and you can access them through methods like Args, NArg, and Arg.

Integer option values can accept forms like 1234 (decimal), 0664 (octal), and 0x1234 (hexadecimal), and can also be negative. In fact, flag internally uses the strconv.ParseInt method to parse the string into an int.

So theoretically, any format accepted by ParseInt is okay.

Boolean option values can be:

  • Values for true: 1, t, T, true, TRUE, True;
  • Values for false: 0, f, F, false, FALSE, False.

Another Way to Define Options

Above, we introduced using flag.TypeVar to define options. This method requires us to first define the variable and then pass in the address of the variable.

There is another way. Calling flag.Type (where Type can be Int, Uint, Bool, Float64, String, Duration, etc.) will automatically allocate a variable for us and return the address of that variable. The usage is similar to the previous way:

package main

import (
  "fmt"
  "flag"
)

var (
  intflag *int
  boolflag *bool
  stringflag *string
)

func init() {
  intflag = flag.Int("intflag", 0, "int flag value")
  boolflag = flag.Bool("boolflag", false, "bool flag value")
  stringflag = flag.String("stringflag", "default", "string flag value")
}

func main() {
  flag.Parse()

  fmt.Println("int flag:", *intflag)
  fmt.Println("bool flag:", *boolflag)
  fmt.Println("string flag:", *stringflag)
}
Enter fullscreen mode Exit fullscreen mode

Compile and run the program:

$ go build -o main main.go
$ ./main -intflag 12
Enter fullscreen mode Exit fullscreen mode

The output will be:

int flag: 12
bool flag: false
string flag: default
Enter fullscreen mode Exit fullscreen mode

Except that dereferencing is required when using it, it is basically the same as the previous method.

Advanced Usage

Defining Short Options

The flag library does not explicitly support short options, but it can be achieved by setting different options for the same variable. That is, two options share the same variable.

Since the initialization order is uncertain, it is necessary to ensure that they have the same default value. Otherwise, when this option is not passed in, the behavior is uncertain.

package main

import (
  "fmt"
  "flag"
)

var logLevel string

func init() {
  const (
    defaultLogLevel = "DEBUG"
    usage = "set log level value"
  )

  flag.StringVar(&logLevel, "log_type", defaultLogLevel, usage)
  flag.StringVar(&logLevel, "l", defaultLogLevel, usage + "(shorthand)")
}

func main() {
  flag.Parse()

  fmt.Println("log level:", logLevel)
}
Enter fullscreen mode Exit fullscreen mode

Compile and run the program:

$ go build -o main main.go
$ ./main -log_type WARNING
$ ./main -l WARNING
Enter fullscreen mode Exit fullscreen mode

Using both long and short options will output:

log level: WARNING
Enter fullscreen mode Exit fullscreen mode

If you don't pass in this option, it will output the default value:

$ ./main
log level: DEBUG
Enter fullscreen mode Exit fullscreen mode

Parsing Time Intervals

In addition to using basic types as options, the flag library also supports the time.Duration type, that is, time intervals. The formats supported for time intervals are very diverse, such as "300ms", "-1.5h", "2h45m", and so on.

The time units can be ns, us, ms, s, m, h, day, etc. In fact, flag will internally call time.ParseDuration. The specific supported formats can be found in the documentation of the time library.

package main

import (
  "flag"
  "fmt"
  "time"
)

var (
  period time.Duration
)

func init() {
  flag.DurationVar(&period, "period", 1*time.Second, "sleep period")
}

func main() {
  flag.Parse()
  fmt.Printf("Sleeping for %v...", period)
  time.Sleep(period)
  fmt.Println()
}
Enter fullscreen mode Exit fullscreen mode

According to the passed command-line option period, the program will sleep for the corresponding time, with a default of 1 second. Compile and run the program:

$ go build -o main main.go
$ ./main
Sleeping for 1s...

$ ./main -period 1m30s
Sleeping for 1m30s...
Enter fullscreen mode Exit fullscreen mode

Customizing Options

In addition to using the option types provided by the flag library, we can also customize option types. Let's analyze the example provided in the standard library:

package main

import (
  "errors"
  "flag"
  "fmt"
  "strings"
  "time"
)

type interval []time.Duration

func (i *interval) String() string {
  return fmt.Sprint(*i)
}

func (i *interval) Set(value string) error {
  if len(*i) > 0 {
    return errors.New("interval flag already set")
  }
  for _, dt := range strings.Split(value, ",") {
    duration, err := time.ParseDuration(dt)
    if err != nil {
      return err
    }
    *i = append(*i, duration)
  }
  return nil
}

var (
  intervalFlag interval
)

func init() {
  flag.Var(&intervalFlag, "deltaT", "comma-seperated list of intervals to use between events")
}

func main() {
  flag.Parse()

  fmt.Println(intervalFlag)
}
Enter fullscreen mode Exit fullscreen mode

First, define a new type. Here, the type interval is defined.

The new type must implement the flag.Value interface:

// src/flag/flag.go
type Value interface {
  String() string
  Set(string) error
}
Enter fullscreen mode Exit fullscreen mode

The String method formats the value of this type, and when the flag.Parse method is executed and encounters an option of the custom type, it will call the Set method of the variable of this type with the option value as the parameter.

Here, the time intervals separated by , are parsed and stored in a slice.

The definition of custom type options must use the flag.Var method.

Compile and execute the program:

$ go build -o main main.go
$ ./main -deltaT 30s
[30s]
$ ./main -deltaT 30s,1m,1m30s
[30s 1m0s 1m30s]
Enter fullscreen mode Exit fullscreen mode

If the specified option value is illegal, the Set method returns a value of type error, the execution of Parse stops, and the error and usage help are printed.

$ ./main -deltaT 30x
invalid value "30x" for flag -deltaT: time: unknown unit x in duration 30x
Usage of /path/to/main:
  -deltaT value
        comma-seperated list of intervals to use between events
Enter fullscreen mode Exit fullscreen mode

Parsing Strings in the Program

Sometimes options are not passed through the command line. For example, they are read from a configuration table or generated by the program. In this case, you can use the relevant methods of the flag.FlagSet structure to parse these options.

In fact, the methods of the flag library we called earlier will all indirectly call the methods of the FlagSet structure. The flag library defines a global variable CommandLine of type FlagSet specifically for parsing command-line options.

The methods of the flag library we called earlier are just for convenience, and internally they all call the corresponding methods of CommandLine:

// src/flag/flag.go
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)

func Parse() {
  CommandLine.Parse(os.Args[1:])
}

func IntVar(p *int, name string, value int, usage string) {
  CommandLine.Var(newIntValue(value, p), name, usage)
}

func Int(name string, value int, usage string) *int {
  return CommandLine.Int(name, value, usage)
}

func NFlag() int { return len(CommandLine.actual) }

func Arg(i int) string {
  return CommandLine.Arg(i)
}

func NArg() int { return len(CommandLine.args) }
Enter fullscreen mode Exit fullscreen mode

Similarly, we can also create our own variables of type FlagSet to parse options.

package main

import (
  "flag"
  "fmt"
)

func main() {
  args := []string{"-intflag", "12", "-stringflag", "test"}

  var intflag int
  var boolflag bool
  var stringflag string

  fs := flag.NewFlagSet("MyFlagSet", flag.ContinueOnError)
  fs.IntVar(&intflag, "intflag", 0, "int flag value")
  fs.BoolVar(&boolflag, "boolflag", false, "bool flag value")
  fs.StringVar(&stringflag, "stringflag", "default", "string flag value")

  fs.Parse(args)

  fmt.Println("int flag:", intflag)
  fmt.Println("bool flag:", boolflag)
  fmt.Println("string flag:", stringflag)
}
Enter fullscreen mode Exit fullscreen mode

The NewFlagSet method has two parameters. The first parameter is the program name, which will be displayed when outputting help or when an error occurs. The second parameter is how to handle errors during parsing, and there are several options:

  • ContinueOnError: Continue parsing after an error occurs. CommandLine uses this option;
  • ExitOnError: Call os.Exit(2) to exit the program when an error occurs;
  • PanicOnError: Generate a panic when an error occurs.

Take a quick look at the relevant code in the flag library:

// src/flag/flag.go
func (f *FlagSet) Parse(arguments []string) error {
  f.parsed = true
  f.args = arguments
  for {
    seen, err := f.parseOne()
    if seen {
      continue
    }
    if err == nil {
      break
    }
    switch f.errorHandling {
    case ContinueOnError:
      return err
    case ExitOnError:
      os.Exit(2)
    case PanicOnError:
      panic(err)
    }
  }
  return nil
}
Enter fullscreen mode Exit fullscreen mode

It's a bit different from directly using the methods of the flag library. When the FlagSet calls the Parse method, it needs to explicitly pass in a slice of strings as a parameter. Because flag.Parse internally calls CommandLine.Parse(os.Args[1:]).

Leapcell: The Next-Gen Serverless Platform for Web Hosting

Finally, I'd like to recommend a platform that is most suitable for deploying Go services: Leapcell

Image description

1. Multi-Language Support

  • Develop with JavaScript, Python, Go, or Rust.

2. Deploy unlimited projects for free

  • pay only for usage — no requests, no charges.

3. Unbeatable Cost Efficiency

  • Pay-as-you-go with no idle charges.
  • Example: $25 supports 6.94M requests at a 60ms average response time.

4. Streamlined Developer Experience

  • Intuitive UI for effortless setup.
  • Fully automated CI/CD pipelines and GitOps integration.
  • Real-time metrics and logging for actionable insights.

5. Effortless Scalability and High Performance

  • Auto-scaling to handle high concurrency with ease.
  • Zero operational overhead — just focus on building.

Image description

Explore more in the documentation!

Leapcell Twitter: https://x.com/LeapcellHQ

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)