Skip to content

Go CLI Applications — Building Command-Line Tools with flag, cobra, and os/exec

DodaTech Updated 2026-06-28 4 min read

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" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse Go