CLI Cookbook: Stdlib and Cobra Examples
CLI Cookbook: Stdlib and Cobra Examples
Overview
Runnable sketches you can paste into a module. §A–§F are stdlib-only. §G–§H use Cobra. Trim imports and split packages as needed.
mkdir -p /tmp/cookbook && cd /tmp/cookbook
go mod init example.com/cookbookA. hello — flags + exit codes (stdlib)
package main
import (
"errors"
"flag"
"fmt"
"io"
"os"
)
var errUsage = errors.New("usage")
func main() {
os.Exit(realMain(os.Args[1:], os.Stdout, os.Stderr))
}
func realMain(args []string, stdout, stderr io.Writer) int {
if err := run(args, stdout, stderr); err != nil {
fmt.Fprintln(stderr, err)
if errors.Is(err, errUsage) {
return 2
}
return 1
}
return 0
}
func run(args []string, stdout, stderr io.Writer) error {
fs := flag.NewFlagSet("hello", flag.ContinueOnError)
fs.SetOutput(stderr)
n := fs.Int("n", 1, "times")
name := fs.String("name", "world", "name")
if err := fs.Parse(args); err != nil {
return errUsage
}
for i := 0; i < *n; i++ {
fmt.Fprintf(stdout, "hello, %s\n", *name)
}
return nil
}go run . -name Ada -n 2B. catn — files or stdin (stdlib)
package main
import (
"flag"
"fmt"
"io"
"os"
)
func main() {
flag.Parse()
args := flag.Args()
if len(args) == 0 {
if _, err := io.Copy(os.Stdout, os.Stdin); err != nil {
fail(err)
}
return
}
for _, p := range args {
if p == "-" {
if _, err := io.Copy(os.Stdout, os.Stdin); err != nil {
fail(err)
}
continue
}
f, err := os.Open(p)
if err != nil {
fail(err)
}
_, err = io.Copy(os.Stdout, f)
f.Close()
if err != nil {
fail(err)
}
}
}
func fail(err error) {
fmt.Fprintln(os.Stderr, "catn:", err)
os.Exit(1)
}echo hi | go run .
go run . /etc/hostsC. ffind — walk files by suffix (stdlib)
package main
import (
"flag"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
)
func main() {
root := flag.String("root", ".", "start directory")
suffix := flag.String("ext", ".go", "file suffix")
flag.Parse()
err := filepath.WalkDir(*root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
if d.Name() == ".git" || d.Name() == "node_modules" {
return fs.SkipDir
}
return nil
}
if strings.HasSuffix(path, *suffix) {
fmt.Println(path)
}
return nil
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}D. jqq — extract JSON field from NDJSON stdin (stdlib)
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
)
func main() {
field := flag.String("f", "", "field name")
flag.Parse()
if *field == "" {
fmt.Fprintln(os.Stderr, "usage: jqq -f field < ndjson")
os.Exit(2)
}
dec := json.NewDecoder(os.Stdin)
enc := json.NewEncoder(os.Stdout)
for {
var m map[string]any
if err := dec.Decode(&m); err != nil {
break
}
if v, ok := m[*field]; ok {
_ = enc.Encode(v)
}
}
}printf '%s\n' '{"user":"a"}' '{"user":"b"}' | go run . -f userE. httpc — GET with timeout (stdlib)
package main
import (
"context"
"flag"
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
url := flag.String("url", "", "URL")
timeout := flag.Duration("timeout", 10*time.Second, "timeout")
flag.Parse()
if *url == "" {
fmt.Fprintln(os.Stderr, "usage: httpc -url URL")
os.Exit(2)
}
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, *url, nil)
if err != nil {
fail(err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
fail(err)
}
defer resp.Body.Close()
fmt.Fprintln(os.Stderr, resp.Status)
_, _ = io.Copy(os.Stdout, io.LimitReader(resp.Body, 2<<20))
}
func fail(err error) { fmt.Fprintln(os.Stderr, err); os.Exit(1) }F. kv — subcommands with FlagSet (stdlib)
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"sync"
)
type DB struct {
mu sync.Mutex
Data map[string]string `json:"data"`
path string
}
func openDB(path string) (*DB, error) {
db := &DB{Data: map[string]string{}, path: path}
b, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return db, nil
}
return nil, err
}
if err := json.Unmarshal(b, db); err != nil {
return nil, err
}
if db.Data == nil {
db.Data = map[string]string{}
}
return db, nil
}
func (db *DB) save() error {
db.mu.Lock()
defer db.mu.Unlock()
b, err := json.MarshalIndent(db, "", " ")
if err != nil {
return err
}
return os.WriteFile(db.path, b, 0o600)
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: kv <get|set|list> ...")
os.Exit(2)
}
path := os.Getenv("KV_DB")
if path == "" {
path = "kv.json"
}
db, err := openDB(path)
if err != nil {
fail(err)
}
switch os.Args[1] {
case "get":
err = cmdGet(db, os.Args[2:])
case "set":
err = cmdSet(db, os.Args[2:])
case "list":
err = cmdList(db, os.Args[2:])
default:
fmt.Fprintln(os.Stderr, "unknown command")
os.Exit(2)
}
if err != nil {
fail(err)
}
}
func cmdGet(db *DB, args []string) error {
fs := flag.NewFlagSet("get", flag.ContinueOnError)
if err := fs.Parse(args); err != nil {
return err
}
if fs.NArg() != 1 {
return fmt.Errorf("usage: kv get KEY")
}
db.mu.Lock()
v, ok := db.Data[fs.Arg(0)]
db.mu.Unlock()
if !ok {
return fmt.Errorf("not found")
}
fmt.Println(v)
return nil
}
func cmdSet(db *DB, args []string) error {
fs := flag.NewFlagSet("set", flag.ContinueOnError)
if err := fs.Parse(args); err != nil {
return err
}
if fs.NArg() != 2 {
return fmt.Errorf("usage: kv set KEY VALUE")
}
db.mu.Lock()
db.Data[fs.Arg(0)] = fs.Arg(1)
db.mu.Unlock()
return db.save()
}
func cmdList(db *DB, args []string) error {
db.mu.Lock()
defer db.mu.Unlock()
for k, v := range db.Data {
fmt.Printf("%s\t%s\n", k, v)
}
return nil
}
func fail(err error) { fmt.Fprintln(os.Stderr, err); os.Exit(1) }go run . set hello world
go run . get hello
go run . listG. Cobra todo — add / list (library)
go get github.com/spf13/cobra@latestpackage main
import (
"encoding/json"
"fmt"
"os"
"time"
"github.com/spf13/cobra"
)
type Task struct {
Text string `json:"text"`
At time.Time `json:"at"`
}
func load(path string) ([]Task, error) {
b, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
var t []Task
return t, json.Unmarshal(b, &t)
}
func save(path string, tasks []Task) error {
b, err := json.MarshalIndent(tasks, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, b, 0o600)
}
func main() {
path := "tasks.json"
root := &cobra.Command{Use: "todo", Short: "Tiny todo list"}
add := &cobra.Command{
Use: "add TEXT...",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
tasks, err := load(path)
if err != nil {
return err
}
text := args[0]
for _, a := range args[1:] {
text += " " + a
}
tasks = append(tasks, Task{Text: text, At: time.Now().UTC()})
return save(path, tasks)
},
}
list := &cobra.Command{
Use: "list",
RunE: func(cmd *cobra.Command, args []string) error {
tasks, err := load(path)
if err != nil {
return err
}
for i, t := range tasks {
fmt.Printf("%d.\t%s\t(%s)\n", i+1, t.Text, t.At.Format(time.RFC3339))
}
return nil
},
}
root.AddCommand(add, list)
if err := root.Execute(); err != nil {
os.Exit(1)
}
}go run . add write docs
go run . listH. Cobra parent + persistent verbose
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
func main() {
var verbose bool
root := &cobra.Command{Use: "tool"}
root.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose")
ping := &cobra.Command{
Use: "ping",
RunE: func(cmd *cobra.Command, args []string) error {
if verbose {
fmt.Fprintln(os.Stderr, "pinging...")
}
fmt.Println("pong")
return nil
},
}
root.AddCommand(ping)
_ = root.Execute()
}go run . -v pingI. Ideas to implement yourself
| Tool | Skills |
|---|---|
sha1dir |
walk + crypto/sha256 + parallel with errgroup |
portwait |
dial loop + timeout + signals |
envshow |
filter os.Environ with regex |
jwtlook |
base64 decode payload (no verify) for debugging |
modgraph |
go mod graph via exec + format |
tailf |
poll file size + print deltas |
csv2json |
encoding/csv + json encoder |
watchrun |
poll mtime + exec command |
J. Full project: network diagnostics CLI
For a multi-command ping / dns / scan / probe toolkit with layout, stats, workers, and tests, follow the dedicated project chapter:
K. Full project: network security mini-tools
For minimal security helpers (TLS cert, headers, banner, redirects, CIDR, cookies, hash, secret scan, rand, listen):
→ 318 Project: Simple Network Security Mini-Tools
Use stdlib until the command tree aches—then port the same domain to Cobra.
Cookbook checklist
- stdout data / stderr logs
- non-zero exit on failure
ContinueOnErrorFlagSet or CobraRunE- context on network/exec
- at least one table test on
run/realMain
When those hold, your CLI is ready to grow.