Cobra Basics
Cobra Basics
Overview
Cobra is the most common Go library for rich CLIs (kubectl, hugo, gh patterns). It gives you command trees, POSIX flags (via pflag), help generation, and hooks for completion.
Use Cobra when stdlib FlagSet dispatch becomes repetitive—not for every 50-line script.
Install
mkdir mytool && cd mytool
go mod init example.com/mytool
go get github.com/spf13/cobra@latest
# optional generator:
go install github.com/spf13/cobra-cli@latestMinimal root + subcommand
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
func main() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
var rootCmd = &cobra.Command{
Use: "greet",
Short: "A friendly greeter",
Long: "greet prints a greeting with optional flair.",
// Run when user types bare `greet` (optional)
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
var helloCmd = &cobra.Command{
Use: "hello [name]",
Short: "Say hello",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := "world"
if len(args) == 1 {
name = args[0]
}
times, _ := cmd.Flags().GetInt("times")
for i := 0; i < times; i++ {
fmt.Printf("hello, %s\n", name)
}
return nil
},
}
func init() {
helloCmd.Flags().IntP("times", "n", 1, "repeat count")
rootCmd.AddCommand(helloCmd)
}go run . hello Ada -n 2
go run . hello --helpRun vs RunE
| Field | Behavior |
|---|---|
Run |
func(cmd, args) — errors must be handled inside |
RunE |
returns error — Cobra prints and sets exit code |
Prefer RunE for testable, consistent errors.
Args validators
Args: cobra.ExactArgs(1),
Args: cobra.MinimumNArgs(1),
Args: cobra.NoArgs,
Args: cobra.OnlyValidArgs, // with ValidArgsCustom:
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("requires a key")
}
return nil
},Local flags
// only on this command
helloCmd.Flags().String("lang", "en", "language")Project layout with Cobra
cmd/
root.go // rootCmd, Execute()
hello.go // helloCmd + init AddCommand
version.go
main.go // cmd.Execute()
// main.go
package main
import "example.com/mytool/cmd"
func main() {
cmd.Execute()
}// cmd/root.go
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{Use: "mytool", Short: "demo"}
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}Example: kv with Cobra
var getCmd = &cobra.Command{
Use: "get KEY",
Short: "Get a value",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
raw, _ := cmd.Flags().GetBool("raw")
val, ok := store[args[0]]
if !ok {
return fmt.Errorf("not found")
}
if raw {
fmt.Print(val)
return nil
}
fmt.Printf("%s=%s\n", args[0], val)
return nil
},
}
func init() {
getCmd.Flags().Bool("raw", false, "raw value only")
rootCmd.AddCommand(getCmd)
}Silencing usage on errors
rootCmd.SilenceUsage = true // don't dump full help on every runtime error
rootCmd.SilenceErrors = true // handle printing yourselfUseful once commands stabilize—usage on every “not found” is noisy.
Mapping to stdlib skills
| Stdlib | Cobra |
|---|---|
switch os.Args[1] |
AddCommand tree |
flag.FlagSet |
cmd.Flags() |
flag.Args() |
args in RunE |
| manual help text | auto --help |
Rules of thumb
| Do | Don’t |
|---|---|
Keep domain out of cmd package |
Put SQL in RunE closures forever |
Use RunE |
Ignore errors in Run |
| One file per command group as you grow | 2k-line root.go |
Try next
- Port the stdlib
greettool to Cobra with-n. - Add
versionsubcommand. - Set
SilenceUsageand compare error UX.