Go CLI Applications — Building Command-Line Tools with flag, cobra, and os/exec
In this tutorial, you will learn about Go CLI Applications. We cover key concepts, practical examples, and best practices to help you master this topic.
Go CLI applications use flag package for simple args, cobra for complex commands, and os/exec for running external processes.
What You'll Learn
- flag package for CLI args
- cobra for complex CLIs
- os/exec for running commands
- CLI input/output patterns
Why It Matters
CLI tools are Go's strength. Docker, Kubernetes CLI (kubectl), Terraform, and Hugo are Go CLIs. DodaZIP uses CLI for file processing.
Real-World Use
DevOps tools, build systems, database management, deployment automation, developer tooling.
flowchart LR
A["CLI Apps"] --> B["flag"]
A --> C["cobra"]
A --> D["os/exec"]
A --> E["Input/Output"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
flag Package
func main() {
name := flag.String("name", "World", "name to greet")
count := flag.Int("count", 1, "number of times")
verbose := flag.Bool("verbose", false, "verbose output")
flag.Parse()
for i := 0; i < *count; i++ {
if *verbose {
fmt.Printf("Greeting %d: ", i+1)
}
fmt.Printf("Hello, %s!\n", *name)
}
}
cobra CLI
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "MyApp is a CLI tool",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Welcome to MyApp")
},
}
var addCmd = &cobra.Command{
Use: "add [numbers...]",
Short: "Add numbers",
Run: func(cmd *cobra.Command, args []string) {
sum := 0
for _, arg := range args {
n, _ := strconv.Atoi(arg)
sum += n
}
fmt.Println("Sum:", sum)
},
}
var verbose bool
func init() {
rootCmd.AddCommand(addCmd)
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
}
func main() {
rootCmd.Execute()
}
os/exec Commands
func runCommand(name string, args ...string) (string, error) {
cmd := exec.Command(name, args...)
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("command failed: %w\n%s", err, output)
}
return string(output), nil
}
func main() {
// Run a command
out, err := runCommand("ls", "-la")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
// With stdin
grepCmd := exec.Command("grep", "main")
grepCmd.Stdin = strings.NewReader("hello\nmain\nworld\nmain")
out, _ = grepCmd.CombinedOutput()
fmt.Println(string(out))
}
Progress Bar
func main() {
bar := progressbar.New(100)
for i := 0; i < 100; i++ {
bar.Add(1)
time.Sleep(50 * time.Millisecond)
}
}
Common Mistakes
1. Not Handling Signals
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
go func() { <-sig; cleanup(); os.Exit(0) }()
2. Ignoring Exit Codes
os.Exit(1) // Non-zero for errors
os.Exit(0) // Success
3. Buffering Without Flush
Flush output after progress updates. Use line-buffered output.
4. Not Using Flag.Args()
flag.Parse()
args := flag.Args() // Non-flag arguments
5. Too Verbose Output
Provide -quiet and -verbose flags. Default to concise output.
Practice Questions
1. What is the difference between flag and cobra? flag for simple flags. cobra for complex CLIs with subcommands, help, and autocomplete.
2. How do you run external commands? exec.Command(name, args...). Call .Run(), .Output(), or .CombinedOutput().
3. How do you read from stdin? os.Stdin implements io.Reader. Use bufio.Scanner or io.ReadAll.
4. What is command composition? Nesting cobra subcommands. Each command has its own flags and Run function.
Challenge: Build a CLI tool that counts words, lines, and characters in a file.
Solution
var rootCmd = &cobra.Command{
Use: "wcgo [file]",
Short: "Count words, lines, and chars",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
data, _ := os.ReadFile(args[0])
lines := strings.Count(string(data), "\n")
words := len(strings.Fields(string(data)))
chars := len(data)
fmt.Printf("%6d %6d %6d %s\n", lines, words, chars, args[0])
},
}
FAQ
{{< faq question="How do I add color to CLI output?" >}} Use ANSI escape codes: "\033[31mRed\033[0m". Or use the fatih/color package. {{< /faq >}}
{{< faq question="How do I handle user input?" >}} Use bufio.NewScanner(os.Stdin) for line input. Use term.ReadPassword for password input. {{< /faq >}}
{{< faq question="How do I build cross-platform CLIs?" >}}
Set GOOS and GOARCH: GOOS=linux GOARCH=amd64 go build. Use build tags for platform-specific code.
{{< /faq >}}
{{< faq question="What is the best CLI framework?" >}} cobra is the most popular. It powers Docker, Kubernetes, and Hugo. urfave/cli is another good option. {{< /faq >}}
{{< faq question="How do I auto-generate bash completion?" >}}
cobra has built-in completion: cmd.GenBashCompletion(os.Stdout). Install with source <(myapp completion bash).
{{< /faq >}}
Try It Yourself
package main
import (
"flag"
"fmt"
)
func main() {
name := flag.String("name", "World", "name to greet")
flag.Parse()
fmt.Printf("Hello, %s!\n", *name)
}
Run with:
go run main.go -name "Go Developer"
Expected output:
Hello, Go Developer!
What's Next
Now that you understand CLI apps, explore web frameworks in Go.
| Topic | Description | Link |
|---|---|---|
| Go Web Frameworks | Web frameworks | {{< ref "40-web-frameworks" >}} |
| Go Middleware | HTTP middleware | {{< ref "41-middleware" >}} |
| Go Testing HTTP | Testing HTTP handlers | {{< ref "42-testing-http" >}} |