
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
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.
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)
}
On running the above code while passing arguments we get the following output:
$ go run main.go --name="John" --age=30 --married=true --duration=1m
Name: John
Age: 30
Married: true
Duration: 1m0s
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")
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
}
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.
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)
}
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.
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)
}
on running the above code with the following arguments:
$ go run main.go --name="Mark Orlan" --name="John Doe"
we get the following output:
Names: [Mark Orlan John Doe]
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.
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)
}
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:
$ go run main.go --name="John" --age=30 subcommand --name="Mark Orlan" --age=40
The output of the above code is:
Name: John
Age: 30
Subcommand Name: Mark Orlan
Subcommand Age: 40
Error Handling
Three error handling strategies can be used with the flag
package. These are:
-
flag.ContinueOnError
- This is the default error handling strategy. This strategy continues parsing the flags even if an error is encountered. -
flag.ExitOnError
- This strategy exits the program if an error is encountered. -
flag.PanicOnError
- This strategy panics if an error is encountered.
The error handling strategy can be set when creating a new flag set using the NewFlagSet
function.
subcommand := flag.NewFlagSet("subcommand", flag.ExitOnError)
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!