Skip to main content
Some command-line tools, like the go tool or git, have many subcommands, each with its own set of flags. For example, go build and go get are two different subcommands of the go tool. The flag package lets us easily define simple subcommands that have their own flags.

Implementing Subcommands

Subcommands are created using flag.NewFlagSet(), which allows each subcommand to have its own set of flags:
package main

import (
	"flag"
	"fmt"
	"os"
)

func main() {

	// We declare a subcommand using the `NewFlagSet`
	// function, and proceed to define new flags specific
	// for this subcommand.
	fooCmd := flag.NewFlagSet("foo", flag.ExitOnError)
	fooEnable := fooCmd.Bool("enable", false, "enable")
	fooName := fooCmd.String("name", "", "name")

	// For a different subcommand we can define different
	// supported flags.
	barCmd := flag.NewFlagSet("bar", flag.ExitOnError)
	barLevel := barCmd.Int("level", 0, "level")

	// The subcommand is expected as the first argument
	// to the program.
	if len(os.Args) < 2 {
		fmt.Println("expected 'foo' or 'bar' subcommands")
		os.Exit(1)
	}

	// Check which subcommand is invoked.
	switch os.Args[1] {

	// For every subcommand, we parse its own flags and
	// have access to trailing positional arguments.
	case "foo":
		fooCmd.Parse(os.Args[2:])
		fmt.Println("subcommand 'foo'")
		fmt.Println("  enable:", *fooEnable)
		fmt.Println("  name:", *fooName)
		fmt.Println("  tail:", fooCmd.Args())
	case "bar":
		barCmd.Parse(os.Args[2:])
		fmt.Println("subcommand 'bar'")
		fmt.Println("  level:", *barLevel)
		fmt.Println("  tail:", barCmd.Args())
	default:
		fmt.Println("expected 'foo' or 'bar' subcommands")
		os.Exit(1)
	}
}

Usage Examples

First, build the program:
go build command-line-subcommands.go

Using the ‘foo’ Subcommand

./command-line-subcommands foo -enable -name=joe a1 a2

Using the ‘bar’ Subcommand

./command-line-subcommands bar -level 8 a1

Flag Isolation

Each subcommand has its own set of flags. A subcommand won’t accept flags from another:
./command-line-subcommands bar -enable a1
Subcommands maintain strict flag isolation - flags defined for one subcommand are not available to others.

Implementation Pattern

1

Create FlagSets

Use flag.NewFlagSet() to create a separate flag set for each subcommand:
fooCmd := flag.NewFlagSet("foo", flag.ExitOnError)
barCmd := flag.NewFlagSet("bar", flag.ExitOnError)
2

Define Flags

Define flags specific to each subcommand using the flag set methods:
fooEnable := fooCmd.Bool("enable", false, "enable")
barLevel := barCmd.Int("level", 0, "level")
3

Parse Subcommand

Check os.Args[1] for the subcommand name and route accordingly:
switch os.Args[1] {
case "foo":
    fooCmd.Parse(os.Args[2:])
    // Handle foo subcommand
case "bar":
    barCmd.Parse(os.Args[2:])
    // Handle bar subcommand
}
4

Handle Arguments

Access trailing positional arguments using the flag set’s Args() method:
fmt.Println("tail:", fooCmd.Args())

Key Concepts

NewFlagSet

Creates an independent flag set with its own flags and parsing behavior.

ExitOnError

The second parameter determines error handling. flag.ExitOnError exits on parsing errors.

Parse Slicing

Use os.Args[2:] when parsing to skip the program name and subcommand name.

Positional Args

Access remaining arguments after flags using the flag set’s Args() method.
For more complex CLI applications with many subcommands, consider using third-party libraries like cobra or cli.

Command Line Flags

Learn the basics of flag parsing in Go

Environment Variables

Another common way to parameterize programs

Build docs developers (and LLMs) love