Go CLI Tools
Cobra Basics
Root command structure
// cmd/root.go
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "netcheck",
Short: "Network diagnostic tool",
Long: "A CLI tool for network diagnostics and host scanning.",
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Cobra structures CLI apps as a tree of commands. The root command is the entry point. Subcommands attach to it.
Subcommands
Adding a subcommand with flags
// cmd/scan.go
var scanCmd = &cobra.Command{
Use: "scan [host]",
Short: "Scan a host for open ports",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
host := args[0]
ports, _ := cmd.Flags().GetIntSlice("ports")
timeout, _ := cmd.Flags().GetDuration("timeout")
fmt.Printf("Scanning %s (ports: %v, timeout: %s)\n",
host, ports, timeout)
return nil
},
}
func init() {
rootCmd.AddCommand(scanCmd)
scanCmd.Flags().IntSlice("ports", []int{22, 80, 443}, "ports to scan")
scanCmd.Flags().Duration("timeout", 5*time.Second, "connection timeout")
}
RunE returns an error (preferred over Run). init() registers the subcommand and its flags. Cobra auto-generates help and usage.
Persistent Flags
Flags inherited by all subcommands
func init() {
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().String("config", "", "config file path")
}
Persistent flags are available to the command and all its children. Local flags (.Flags()) apply only to the command itself.
os.Args Alternative — flag Package
Standard library flag — no dependencies
package main
import (
"flag"
"fmt"
)
func main() {
host := flag.String("host", "localhost", "target host")
port := flag.Int("port", 8080, "target port")
verbose := flag.Bool("v", false, "verbose output")
flag.Parse()
fmt.Printf("Connecting to %s:%d (verbose=%t)\n", *host, *port, *verbose)
// Remaining positional args
args := flag.Args()
fmt.Println("Extra args:", args)
}
// Usage: ./tool -host 10.50.1.1 -port 443 -v
The flag package is the zero-dependency option. Use Cobra for complex CLIs with subcommands; flag for simple tools.
JSON Output
Structured CLI output
type ScanResult struct {
Host string `json:"host"`
Port int `json:"port"`
Open bool `json:"open"`
Latency string `json:"latency,omitempty"`
}
func outputResults(results []ScanResult, format string) {
switch format {
case "json":
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(results)
case "table":
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "HOST\tPORT\tSTATUS")
for _, r := range results {
status := "closed"
if r.Open {
status = "open"
}
fmt.Fprintf(w, "%s\t%d\t%s\n", r.Host, r.Port, status)
}
w.Flush()
}
}
CLI tools should support machine-readable output (JSON) for pipeline integration alongside human-readable output (table).