flag Package in Depth
flag Package in Depth
Overview
Package flag is the stdlib CLI parser: bool/int/string/duration flags, custom Value types, and usage text. Master this before Cobra—Cobra’s flags are powered by pflag, a POSIX-friendly fork of the same ideas.
Minimal example
package main
import (
"flag"
"fmt"
"os"
"time"
)
func main() {
var (
name = flag.String("name", "world", "name to greet")
times = flag.Int("n", 1, "repeat count")
verbose = flag.Bool("v", false, "verbose")
timeout = flag.Duration("timeout", 2*time.Second, "budget")
)
flag.Parse()
if *verbose {
fmt.Fprintf(os.Stderr, "timeout=%s args=%v\n", *timeout, flag.Args())
}
for i := 0; i < *times; i++ {
fmt.Printf("hello, %s\n", *name)
}
}go run . -name Ada -n 2 -v -- leftover
# flags after -- are not parsed as flags in some styles;
# with flag, non-flag args are flag.Args() after ParseVar forms (bind to your own vars)
var cfg struct {
Addr string
Port int
}
flag.StringVar(&cfg.Addr, "addr", "127.0.0.1", "listen address")
flag.IntVar(&cfg.Port, "port", 8080, "port")
flag.Parse()Prefer Var when flags map into a config struct.
Custom Usage
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(),
"Usage: %s [flags] <path>\n\nFlags:\n", filepath.Base(os.Args[0]))
flag.PrintDefaults()
}flag.CommandLine.Output() defaults to stderr—keep it that way.
Error handling modes
fs := flag.NewFlagSet("tool", flag.ContinueOnError)
// flag.ExitOnError — print usage, os.Exit(2) (CommandLine default)
// flag.PanicOnError — panic (rare)
// flag.ContinueOnError — return error from Parse (best for tests)For testable tools:
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
fs.SetOutput(io.Discard) // or a buffer when asserting usage
err := fs.Parse(args)Custom flag types (flag.Value)
type stringsFlag []string
func (s *stringsFlag) String() string { return fmt.Sprint(*s) }
func (s *stringsFlag) Set(v string) error {
*s = append(*s, v)
return nil
}
func main() {
var tags stringsFlag
flag.Var(&tags, "tag", "repeatable tag (use multiple -tag)")
flag.Parse()
fmt.Println(tags)
}go run . -tag a -tag b
# [a b]Enum flag
type level string
func (l *level) String() string { return string(*l) }
func (l *level) Set(v string) error {
switch v {
case "debug", "info", "error":
*l = level(v)
return nil
default:
return fmt.Errorf("level must be debug|info|error")
}
}
var lvl level = "info"
flag.Var(&lvl, "level", "log level")Bool flags
verbose := flag.Bool("verbose", false, "verbose")
// -verbose, -verbose=true, -verbose=false
// short -v needs a separate flag or pflag/Cobra
flag.Bool("v", false, "verbose (short)")Stdlib flag does not combine short options (-abc). Use pflag/Cobra for GNU style.
Positional args after flags
flag.Parse()
paths := flag.Args()
if len(paths) == 0 {
fmt.Fprintln(os.Stderr, "need at least one path")
os.Exit(2)
}Order: users typically pass flags first. With stdlib flag, a non-flag stops flag parsing for the rest (classic Go behavior).
Example: httpget (stdlib-only client)
func main() {
url := flag.String("url", "", "URL to GET")
timeout := flag.Duration("timeout", 10*time.Second, "client timeout")
flag.Parse()
if *url == "" {
flag.Usage()
os.Exit(2)
}
client := &http.Client{Timeout: *timeout}
resp, err := client.Get(*url)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer resp.Body.Close()
fmt.Println(resp.Status)
_, _ = io.Copy(os.Stdout, io.LimitReader(resp.Body, 1<<20))
}Example: sleepfor
d := flag.Duration("d", time.Second, "sleep duration")
flag.Parse()
time.Sleep(*d)Example: lines — count lines in files
func main() {
verbose := flag.Bool("v", false, "print per-file counts")
flag.Parse()
total := 0
for _, path := range flag.Args() {
n, err := countLines(path)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if *verbose {
fmt.Printf("%d\t%s\n", n, path)
}
total += n
}
fmt.Println(total)
}Rules of thumb
| Do | Don’t |
|---|---|
| Document every flag default in help | Undocumented env-only secret flags as the only API |
ContinueOnError + run() for tests |
Parse global flag in init of libraries |
Custom Value for repeatable/enum |
Parse enums with silent fallbacks |
Try next
- Add a repeatable
-header k=vflag usingflag.Value. - Write tests that parse a private
FlagSetwithContinueOnError. - Build
greet -n 3 -name Xand verify usage on bad-n=abc.