CLI-Anwendungen in Go mit Cobra & Viper erstellen

CLI-Entwicklung in Go mit den Frameworks Cobra und Viper

Inhaltsverzeichnis

Kommandzeilen-Interface (CLI)-Anwendungen sind essentielle Werkzeuge für Entwickler, Systemadministratoren und DevOps-Professionals. Zwei Go-Bibliotheken haben sich als de facto Standard für CLI-Entwicklung in Go: Cobra für die Befehlsstruktur und Viper für das Konfigurationsmanagement.

Go hat sich als hervorragende Sprache für den Bau von CLI-Tools erwiesen, dank seiner Leistung, einfachen Bereitstellung und plattformübergreifenden Unterstützung.

Tetris

Warum Go für CLI-Anwendungen wählen

Go bietet überzeugende Vorteile für die CLI-Entwicklung:

  • Einzelne Binärverteilung: Keine Laufzeitabhängigkeiten oder Paketmanager erforderlich
  • Schnelle Ausführung: Native Kompilierung bietet hervorragende Leistung
  • Plattformübergreifende Unterstützung: Einfache Kompilierung für Linux, macOS, Windows und mehr
  • Starke Standardbibliothek: Reichhaltige Werkzeuge für Datei-I/O, Netzwerk und Textverarbeitung
  • Konkurrrenzfähigkeit: Eingebaute Goroutines für parallele Operationen
  • Statische Typisierung: Fehler werden zur Kompilierzeit erkannt

Beliebte CLI-Tools, die mit Go erstellt wurden, umfassen Docker, Kubernetes (kubectl), Hugo, Terraform und GitHub CLI. Wenn Sie neu in Go sind oder eine schnelle Referenz benötigen, werfen Sie einen Blick auf unseren Go Cheat Sheet für wesentliche Go-Syntax und Muster.

Einführung in Cobra

Cobra ist eine Bibliothek, die eine einfache Schnittstelle für die Erstellung leistungsfähiger moderner CLI-Anwendungen bietet. Erstellt von Steve Francia (spf13), dem gleichen Autor hinter Hugo und Viper, wird Cobra in vielen der beliebtesten Go-Projekten verwendet.

Wichtige Cobra-Funktionen

Befehlsstruktur: Cobra implementiert das Befehlsmuster, das es Ihnen ermöglicht, Anwendungen mit Befehlen und Unterbefehlen (wie git commit oder docker run) zu erstellen.

Flag-Verarbeitung: Sowohl lokale als auch dauerhafte Flags mit automatischer Analyse und Typumwandlung.

Automatische Hilfe: Generiert Hilfetexte und Nutzungsinformationen automatisch.

Intelligente Vorschläge: Bietet Vorschläge, wenn Benutzer Tippfehler machen (“Meinten Sie ‘status’?”).

Shell-Vervollständigungen: Generiert Vervollständigungsskripts für bash, zsh, fish und PowerShell.

Flexible Ausgabe: Funktioniert nahtlos mit benutzerdefinierten Formatierern und Ausgabestilen.

Einführung in Viper

Viper ist eine vollständige Konfigurationslösung für Go-Anwendungen, die speziell für die nahtlose Zusammenarbeit mit Cobra entwickelt wurde. Es verwaltet Konfigurationen aus mehreren Quellen mit einer klaren Prioritätsreihenfolge.

Wichtige Viper-Funktionen

Mehrere Konfigurationsquellen:

  • Konfigurationsdateien (JSON, YAML, TOML, HCL, INI, envfile, Java-Eigenschaften)
  • Umgebungsvariablen
  • Befehlszeilenflags
  • Remote-Konfigurationssysteme (etcd, Consul)
  • Standardwerte

Konfigurationsprioritätsreihenfolge:

  1. Explizite Aufrufe von Set
  2. Befehlszeilenflags
  3. Umgebungsvariablen
  4. Konfigurationsdatei
  5. Schlüssel-Wert-Speicher
  6. Standardwerte

Live-Überwachung: Überwacht Konfigurationsdateien und lädt automatisch bei Änderungen neu.

Typumwandlung: Automatische Umwandlung in verschiedene Go-Typen (String, int, bool, Dauer, usw.).

Erste Schritte: Installation

Zuerst initialisieren Sie ein neues Go-Modul und installieren Sie beide Bibliotheken:

go mod init myapp
go get -u github.com/spf13/cobra@latest
go get -u github.com/spf13/viper@latest

Optional installieren Sie den Cobra-CLI-Generator für das Gerüst:

go install github.com/spf13/cobra-cli@latest

Aufbau Ihrer ersten CLI-Anwendung

Lassen Sie uns ein praktisches Beispiel erstellen: ein Aufgabenverwaltungs-CLI-Tool mit Konfigurationsunterstützung.

Projektstruktur

mytasks/
├── cmd/
│   ├── root.go
│   ├── add.go
│   ├── list.go
│   └── complete.go
├── config/
│   └── config.go
├── main.go
└── config.yaml

Haupteinstiegspunkt

// main.go
package main

import "mytasks/cmd"

func main() {
    cmd.Execute()
}

Root-Befehl mit Viper-Integration

// cmd/root.go
package cmd

import (
    "fmt"
    "os"
    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var cfgFile string

var rootCmd = &cobra.Command{
    Use:   "mytasks",
    Short: "Ein einfaches Aufgabenverwaltungs-CLI",
    Long: `MyTasks ist ein CLI-Aufgabenmanager, der Ihnen hilft,
Ihre täglichen Aufgaben einfach zu organisieren. Erstellt mit Cobra und Viper.`,
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func init() {
    cobra.OnInitialize(initConfig)

    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "",
        "Konfigurationsdatei (Standard ist $HOME/.mytasks.yaml)")
    rootCmd.PersistentFlags().String("db",
        "Datenbankdatei-Speicherort")

    viper.BindPFlag("database", rootCmd.PersistentFlags().Lookup("db"))
}

func initConfig() {
    if cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        home, err := os.UserHomeDir()
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        viper.AddConfigPath(home)
        viper.AddConfigPath(".")
        viper.SetConfigType("yaml")
        viper.SetConfigName(".mytasks")
    }

    viper.SetEnvPrefix("MYTASKS")
    viper.AutomaticEnv()

    if err := viper.ReadInConfig(); err == nil {
        fmt.Println("Verwendung der Konfigurationsdatei:", viper.ConfigFileUsed())
    }
}

Hinzufügen von Unterbefehlen

// cmd/add.go
package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var priority string

var addCmd = &cobra.Command{
    Use:   "add [Aufgabenbeschreibung]",
    Short: "Fügt eine neue Aufgabe hinzu",
    Args:  cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        task := args[0]
        db := viper.GetString("database")

        fmt.Printf("Aufgabe hinzufügen: %s\n", task)
        fmt.Printf("Priorität: %s\n", priority)
        fmt.Printf("Datenbank: %s\n", db)

        // Hier würden Sie die tatsächliche Aufgabenverwaltung implementieren
    },
}

func init() {
    rootCmd.AddCommand(addCmd)

    addCmd.Flags().StringVarP(&priority, "priority", "p", "medium",
        "Aufgabenpriorität (niedrig, mittel, hoch)")
}

List-Befehl

// cmd/list.go
package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var showCompleted bool

var listCmd = &cobra.Command{
    Use:   "list",
    Short: "Listet alle Aufgaben auf",
    Run: func(cmd *cobra.Command, args []string) {
        db := viper.GetString("database")

        fmt.Printf("Aufgaben auflisten von: %s\n", db)
        fmt.Printf("Abgeschlossene Aufgaben anzeigen: %v\n", showCompleted)

        // Hier würden Sie die tatsächliche Aufgabenliste implementieren
    },
}

func init() {
    rootCmd.AddCommand(listCmd)

    listCmd.Flags().BoolVarP(&showCompleted, "completed", "c", false,
        "Abgeschlossene Aufgaben anzeigen")
}

Implementierung der Datenpersistenz

Für ein Produktionsaufgabenverwaltungs-CLI müssen Sie die tatsächliche Datenspeicherung implementieren. Während wir hier einen einfachen Datenbankpfad verwenden, haben Sie mehrere Optionen zur Persistenz von Daten:

  • SQLite: Leichtgewichtige, serverlose Datenbank, perfekt für CLI-Tools
  • PostgreSQL/MySQL: Vollständige Datenbanken für komplexere Anwendungen
  • JSON/YAML-Dateien: Einfache dateibasierte Speicherung für leichte Anforderungen
  • Eingebettete Datenbanken: BoltDB, BadgerDB für Schlüssel-Wert-Speicherung

Wenn Sie mit relationalen Datenbanken wie PostgreSQL arbeiten, möchten Sie wahrscheinlich ein ORM oder einen Abfragegenerator verwenden. Für einen umfassenden Vergleich von Go-Datenbankbibliotheken sehen Sie sich unsere Anleitung an Vergleich von Go-ORMs für PostgreSQL: GORM vs Ent vs Bun vs sqlc.

Fortgeschrittene Konfiguration mit Viper

Beispiel für Konfigurationsdatei

# .mytasks.yaml
database: ~/.mytasks.db
format: table
colors: true

notifications:
  enabled: true
  sound: true

priorities:
  high: red
  medium: yellow
  low: green

Lesen verschachtelter Konfiguration

func getNotificationSettings() {
    enabled := viper.GetBool("notifications.enabled")
    sound := viper.GetBool("notifications.sound")

    fmt.Printf("Benachrichtigungen aktiviert: %v\n", enabled)
    fmt.Printf("Ton aktiviert: %v\n", sound)
}

func getPriorityColors() map[string]string {
    return viper.GetStringMapString("priorities")
}

Umgebungsvariablen

Viper liest automatisch Umgebungsvariablen mit dem konfigurierten Präfix:

export MYTASKS_DATABASE=/tmp/tasks.db
export MYTASKS_NOTIFICATIONS_ENABLED=false
mytasks list

Live-Konfigurationsneuladung

func watchConfig() {
    viper.WatchConfig()
    viper.OnConfigChange(func(e fsnotify.Event) {
        fmt.Println("Konfigurationsdatei geändert:", e.Name)
        // Konfigurationsabhängige Komponenten neu laden
    })
}

Best Practices

1. Verwenden Sie den Cobra-Generator für Konsistenz

cobra-cli init
cobra-cli add serve
cobra-cli add create

2. Organisieren Sie Befehle in separaten Dateien

Halten Sie jeden Befehl in seiner eigenen Datei unter dem cmd/-Verzeichnis für die Wartbarkeit.

3. Nutzen Sie dauerhafte Flags

Verwenden Sie dauerhafte Flags für Optionen, die auf alle Unterbefehle zutreffen:

rootCmd.PersistentFlags().StringP("output", "o", "text",
    "AusgabefORMAT (text, json, yaml)")

4. Implementieren Sie eine ordnungsgemäße Fehlerbehandlung

var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "Meine Anwendung",
    RunE: func(cmd *cobra.Command, args []string) error {
        if err := doSomething(); err != nil {
            return fmt.Errorf("Fehler beim Ausführen von etwas: %w", err)
        }
        return nil
    },
}

5. Bieten Sie sinnvolle Hilfetexte an

var cmd = &cobra.Command{
    Use:   "deploy [Umgebung]",
    Short: "Anwendung in der angegebenen Umgebung bereitstellen",
    Long: `Bereitstellen baut und stellt Ihre Anwendung in der
angegebenen Umgebung bereit. Unterstützte Umgebungen sind:
  - Entwicklung (dev)
  - Staging
  - Produktion (prod)`,
    Example: `  myapp deploy staging
  myapp deploy production --version=1.2.3`,
}

6. Setzen Sie sinnvolle Standardwerte

func init() {
    viper.SetDefault("port", 8080)
    viper.SetDefault("timeout", "30s")
    viper.SetDefault("retries", 3)
}

7. Validieren Sie die Konfiguration

func validateConfig() error {
    port := viper.GetInt("port")
    if port < 1024 || port > 65535 {
        return fmt.Errorf("ungültiger Port: %d", port)
    }
    return nil
}

Testen von CLI-Anwendungen

Testen von Befehlen

// cmd/root_test.go
package cmd

import (
    "bytes"
    "testing"
)

func TestRootCommand(t *testing.T) {
    cmd := rootCmd
    b := bytes.NewBufferString("")
    cmd.SetOut(b)
    cmd.SetArgs([]string{"--help"})

    if err := cmd.Execute(); err != nil {
        t.Fatalf("command execution failed: %v", err)
    }
}

Testen mit verschiedenen Konfigurationen

func TestWithConfig(t *testing.T) {
    viper.Set("database", "/tmp/test.db")
    viper.Set("debug", true)

    // Führen Sie Ihre Tests aus

    viper.Reset() // Aufräumen
}

Generieren von Shell-Vervollständigungen

Cobra kann Vervollständigungsskripte für verschiedene Shells generieren:

// cmd/completion.go
package cmd

import (
    "os"
    "github.com/spf13/cobra"
)

var completionCmd = &cobra.Command{
    Use:   "completion [bash|zsh|fish|powershell]",
    Short: "Generiert ein Vervollständigungsskript",
    Long: `Um Vervollständigungen zu laden:

Bash:
  $ source <(mytasks completion bash)

Zsh:
  $ source <(mytasks completion zsh)

Fish:
  $ mytasks completion fish | source

PowerShell:
  PS> mytasks completion powershell | Out-String | Invoke-Expression
`,
    DisableFlagsInUseLine: true,
    ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
    Args: cobra.ExactValidArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        switch args[0] {
        case "bash":
            cmd.Root().GenBashCompletion(os.Stdout)
        case "zsh":
            cmd.Root().GenZshCompletion(os.Stdout)
        case "fish":
            cmd.Root().GenFishCompletion(os.Stdout, true)
        case "powershell":
            cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
        }
    },
}

func init() {
    rootCmd.AddCommand(completionCmd)
}

Bauen und Verteilung

Bauen für mehrere Plattformen

# Linux
GOOS=linux GOARCH=amd64 go build -o mytasks-linux-amd64

# macOS
GOOS=darwin GOARCH=amd64 go build -o mytasks-darwin-amd64
GOOS=darwin GOARCH=arm64 go build -o mytasks-darwin-arm64

# Windows
GOOS=windows GOARCH=amd64 go build -o mytasks-windows-amd64.exe

Reduzieren der Binärdateigröße

go build -ldflags="-s -w" -o mytasks

Hinzufügen von Versionsinformationen

// cmd/version.go
package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
)

var (
    Version   = "dev"
    Commit    = "none"
    BuildTime = "unknown"
)

var versionCmd = &cobra.Command{
    Use:   "version",
    Short: "Gibt Versionsinformationen aus",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Printf("Version: %s\n", Version)
        fmt.Printf("Commit: %s\n", Commit)
        fmt.Printf("Built: %s\n", BuildTime)
    },
}

func init() {
    rootCmd.AddCommand(versionCmd)
}

Mit Versionsinformationen bauen:

go build -ldflags="-X 'mytasks/cmd.Version=1.0.0' \
    -X 'mytasks/cmd.Commit=$(git rev-parse HEAD)' \
    -X 'mytasks/cmd.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" \
    -o mytasks

Praxisbeispiele

Beliebte Open-Source-Tools, die mit Cobra und Viper erstellt wurden:

  • kubectl: Kubernetes-Befehlszeilen-Tool
  • Hugo: Statische Website-Generator
  • GitHub CLI (gh): Offizielles GitHub-CLI
  • Docker CLI: Container-Verwaltung
  • Helm: Kubernetes-Paketmanager
  • Skaffold: Kubernetes-Entwicklungs-Workflow-Tool
  • Cobra CLI: Selbsthosting - Cobra verwendet sich selbst!

Über traditionelle DevOps-Tools hinaus erstrecken sich die CLI-Fähigkeiten von Go auf KI- und maschinelles Lernen. Wenn Sie daran interessiert sind, CLI-Tools zu erstellen, die mit großen Sprachmodellen interagieren, werfen Sie einen Blick auf unseren Leitfaden zu Einschränkung von LLMs mit strukturierten Ausgaben unter Verwendung von Ollama und Go, der zeigt, wie man Go-Anwendungen erstellt, die mit KI-Modellen arbeiten.

Nützliche Ressourcen

Fazit

Cobra und Viper bieten zusammen eine leistungsstarke Grundlage für die Erstellung professioneller CLI-Anwendungen in Go. Cobra verwaltet die Befehlsstruktur, Flag-Analyse und Hilfegenerierung, während Viper die Konfiguration aus mehreren Quellen mit intelligenter Priorisierung verwaltet.

Diese Kombination ermöglicht es Ihnen, CLI-Tools zu erstellen, die:

  • Einfach zu bedienen mit intuitiven Befehlen und automatischer Hilfe
  • Flexibel mit mehreren Konfigurationsquellen
  • Professionell mit Shell-Vervollständigungen und korrekter Fehlerbehandlung
  • Wartbar mit sauberer Code-Organisation
  • Plattformübergreifend auf verschiedenen Systemen

Egal, ob Sie Entwicklertools, Systemutilities oder DevOps-Automatisierung erstellen, Cobra und Viper bieten die solide Grundlage, die Sie benötigen, um CLI-Anwendungen zu erstellen, die die Benutzer lieben werden.