author
Kevin Kelche

Mastering Command-Line Flags in Golang


Introduction

Flags are a standard method for passing arguments to command-line applications. They are frequently used to alter the behavior of the application. For example, the go command utilizes multiple flags for tasks such as checking the version, building, running, and others. In this article, we’ll explore how to use the flag package in Go to parse command-line flags.

Creating Flags

The flag package provides various functions, such as String, Int, Bool, Duration, and others to create flags of different types. These functions all return a pointer to the value of the flag. Below is a generic definition of the format:

func type(name string, value type, usage string) *type

Copied!

The type can be of any data type. The name represents the name of the flag, while the value serves as the default value for the flag. The usage string, which is displayed when the --help flag is used, describes the flag’s purpose.

main.go
package main

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

func main() {
    name := flag.String("name", "Kevin", "The name of the user")
    age := flag.Int("age", 26, "The age of the user")
    married := flag.Bool("married", false, "Whether the user is married")
    duration := flag.Duration("duration", 5*time.Second, "The duration of the user")

    flag.Parse()

    fmt.Println("Name:", *name)
    fmt.Println("Age:", *age)
    fmt.Println("Married:", *married)
    fmt.Println("Duration:", *duration)
}

Copied!

On running the above code while passing arguments we get the following output:

terminal
$ go run main.go --name="John" --age=30 --married=true --duration=1m
Name: John
Age: 30
Married: true
Duration: 1m0s

Copied!

The flag.Parse() function, which we have just introduced, is used to parse the command-line flags and assign their values. It’s crucial to remember that flags must be defined before calling this function.

In addition to using a pointer to the flag’s value, there are alternative ways to define strings such as with StringVar and IntVar. These functions require a pointer to the flag’s value as an argument.

var name string
flag.StringVar(&name, "name", "Kevin", "The name of the user")

Copied!

Creating Custom Flags and Validating Values

The flag package offers the ability to create custom flags with the Var function. This function requires three arguments: a value that implements the Value interface, a name for the flag, and a usage string to describe its purpose.

The value interface is defined as follows:

type Value interface {
    String() string
    Set(string) error
}

Copied!

The String method returns the value of the flag as a string. The Set method takes a string and sets the value of the flag. The Set method returns an error if the value is invalid.

main.go
package main

import (
    "flag"
    "fmt"
    "strings"
)

type name struct {
    firstName string
    lastName  string
}

func (n *name) String() string {
    return fmt.Sprintf("%s %s", n.firstName, n.lastName)
}

func (n *name) Set(value string) error {
    if len(value) < 3 {
        return fmt.Errorf("name is too short")
    }

    parts := strings.Split(value, " ")
    if len(parts) != 2 {
        return fmt.Errorf("name must be in the format 'first last'")
    }
    n.firstName = parts[0]
    n.lastName = parts[len(parts)-1]

    return nil
}

func main() {
    var n name
    flag.Var(&n, "name", "The name of the user")

    flag.Parse()

    fmt.Println("Name:", n)
}

Copied!

In this code, we created a custom flag type that accepts a name in the format first last. The Set method validates the value and then splits the name into first and last names.

Taking multiple values from a flag

The Var function and Value interface allow you to create custom, complex flags. You can even create flags that accept multiple values.

main.go
package main

import (
    "flag"
    "fmt"
    "strings"
)

type names []string

func (n *names) String() string {
    return fmt.Sprintf("%s", *n)
}

func (n *names) Set(value string) error {
    if len(value) < 3 {
        return fmt.Errorf("name is too short")
    }

    parts := strings.Split(value, " ")
    if len(parts) != 2 {
        return fmt.Errorf("name must be in the format 'first last'")
    }

    *n = append(*n, value)

    return nil
}

func main() {
    var n names
    flag.Var(&n, "name", "The name of the user")

    flag.Parse()

    fmt.Println("Names:", n)
}

Copied!

on running the above code with the following arguments:

terminal
$ go run main.go --name="Mark Orlan" --name="John Doe"

Copied!

we get the following output:

terminal
Names: [Mark Orlan John Doe]

Copied!

Unlike earlier, where the last value of the flag would overwrite the previous value, in this case, the flag takes multiple values.

Creating Subcommands

The flag package also provides a way to create subcommands. This is done by creating a new flag set and then calling the Parse method on the flag set.

main.go
package main

import (
    "flag"
    "fmt"
)

func main() {
    name := flag.String("name", "Kevin", "The name of the user")
    age := flag.Int("age", 26, "The age of the user")

    flag.Parse()

    fmt.Println("Name:", *name)
    fmt.Println("Age:", *age)

    subcommand := flag.NewFlagSet("subcommand", flag.ExitOnError)
    subcommandName := subcommand.String("name", "Kevin", "The name of the user")
    subcommandAge := subcommand.Int("age", 26, "The age of the user")

    subcommand.Parse(flag.Args()[1:])
    fmt.Println("Subcommand Name:", *subcommandName)
    fmt.Println("Subcommand Age:", *subcommandAge)

}

Copied!

In this code, we’re introduced to the NewFlagSet function. This function creates a new flagSet. The first argument is the name of the subcommand, and the second argument is the error-handling strategy, which will be discussed later.

You can run the above code with the following arguments:

terminal
$ go run main.go --name="John" --age=30 subcommand --name="Mark Orlan" --age=40

Copied!

The output of the above code is:

terminal
Name: John
Age: 30
Subcommand Name: Mark Orlan
Subcommand Age: 40

Copied!

Error Handling

Three error handling strategies can be used with the flag package. These are:

The error handling strategy can be set when creating a new flag set using the NewFlagSet function.

subcommand := flag.NewFlagSet("subcommand", flag.ExitOnError)

Copied!

Conclusion

To conclude, the Golang Flag package is a flag-waving hero for your CLI app adventures! With its customizable flag behavior, you can enhance the user experience of your Go app. From simple flag parsing to advanced techniques such as value validation and programmatic modifications, the Flag package has everything you need. So hoist the flag and start coding!

Subscribe to my newsletter

Get the latest posts delivered right to your inbox.