CLI#
Go exposes command-line arguments through os.Args. The
stdlib ships flag for basic parsing; for subcommands and
richer UX reach for cobra (Kubernetes, Hugo,
GitHub CLI) or urfave/cli (lighter, classic). For options
declared as struct tags, kong is the modern alternative.
For the I/O surface scripts read and write through, see I/O. For packaging a Go binary for release, see Runtime.
os.Args#
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("program:", os.Args[0])
for i, a := range os.Args[1:] {
fmt.Println(i, a)
}
}
$ go run main.go -v --port 8080 host
program: /tmp/go-build123/main
0 -v
1 --port
2 8080
3 host
flag (stdlib)#
flag is the stdlib argument parser; good for one or two
flags, no subcommands.
import "flag"
func main() {
verbose := flag.Bool("v", false, "verbose output")
port := flag.Int("port", 80, "port to probe")
flag.Parse() // parses os.Args[1:]
host := flag.Arg(0)
if host == "" {
fmt.Fprintln(os.Stderr, "usage: scan [-v] [-port N] <host>")
os.Exit(2)
}
fmt.Println(host, *port, *verbose)
}
$ go run main.go -v -port 8080 example.com
example.com 8080 true
For multi-value flags or richer types, implement flag.Value
on a custom type.
cobra#
cobra is the default for non-trivial CLIs.
Subcommands, --help generation, persistent flags, command
groups.
$ go get github.com/spf13/cobra@latest
import "github.com/spf13/cobra"
var (
port int
verbose bool
)
var root = &cobra.Command{
Use: "scan <host>",
Short: "Quick port scanner.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
host := args[0]
fmt.Println(host, port, verbose)
return nil
},
}
func init() {
root.Flags().IntVarP(&port, "port", "p", 80, "port to probe")
root.Flags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
}
func main() {
if err := root.Execute(); err != nil { os.Exit(1) }
}
For subcommands.
var upCmd = &cobra.Command{
Use: "up",
RunE: func(cmd *cobra.Command, args []string) error { return start() },
}
var downCmd = &cobra.Command{
Use: "down",
RunE: func(cmd *cobra.Command, args []string) error { return stop() },
}
func init() { root.AddCommand(upCmd, downCmd) }
kong (declarative)#
kong parses a struct of fields tagged with command-line
metadata. Less boilerplate when the options are stable.
$ go get github.com/alecthomas/kong
import "github.com/alecthomas/kong"
var cli struct {
Verbose bool `short:"v" help:"Verbose output."`
Port int `short:"p" default:"80" help:"Port to probe."`
Host string `arg:"" help:"Target host."`
}
func main() {
_ = kong.Parse(&cli)
fmt.Println(cli.Host, cli.Port, cli.Verbose)
}
Exit codes#
os.Exit(n) exits with code n. Convention: 0 on
success, small positive integer on failure. os.Exit does
not run deferred functions; the operator returns from main
when possible.
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
Streaming#
Filter-style scripts read stdin, transform, write stdout.
sc := bufio.NewScanner(os.Stdin)
w := bufio.NewWriter(os.Stdout)
defer w.Flush()
for sc.Scan() {
fmt.Fprintln(w, strings.ToUpper(sc.Text()))
}
$ echo -e "one\ntwo" | ./upper
ONE
TWO
Signals and shutdown#
os/signal lets the operator catch SIGINT / SIGTERM and shut
down cleanly.
import (
"context"
"os/signal"
"syscall"
)
ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer stop()
if err := serve(ctx); err != nil { return err }
The signal cancels the context; thread the context into every long call so it tears down on Ctrl-C.