Bygga CLI-appar i Go med Cobra & Viper

CLI-utveckling i Go med Cobra- och Viper-ramverken

Sidinnehåll

Kommandoradsgränssnitt (CLI) applikationer är essentiella verktyg för utvecklare, systemadministratörer och DevOps-professionella. Två Go-bibliotek har blivit de faktiska standarderna för CLI-utveckling i Go: Cobra för kommandostruktur och Viper för konfigurationshantering.

Go har etablerat sig som ett utmärkt språk för att bygga CLI-verktyg tack vare dess prestanda, enkel distribution och plattformsoberoende stöd.

Tetris

Varför välja Go för CLI-applikationer

Go erbjuder övertygande fördelar för CLI-utveckling:

  • Enkel binär distribution: Inga runtimeberoenden eller pakethanterare krävs
  • Snabb exekvering: Nativ kompilering ger utmärkt prestanda
  • Plattformsoberoende stöd: Enkel kompilering för Linux, macOS, Windows och fler
  • Starkt standardbibliotek: Rikt verktyg för fil-I/O, nätverk och textbehandling
  • Konkurrensthantering: Inbyggda goroutines för parallella operationer
  • Statisk typning: Upptäck fel vid kompileringstid

Populära CLI-verktyg byggda med Go inkluderar Docker, Kubernetes (kubectl), Hugo, Terraform och GitHub CLI. Om du är ny på Go eller behöver en snabb referens, kolla in vårt Go Cheat Sheet för grundläggande Go-syntax och mönster.

Introduktion till Cobra

Cobra är ett bibliotek som tillhandahåller ett enkelt gränssnitt för att skapa kraftfulla moderna CLI-applikationer. Skapat av Steve Francia (spf13), samma författare bakom Hugo och Viper, används Cobra i många av de mest populära Go-projekten.

Nyckelfunktioner i Cobra

Kommandostruktur: Cobra implementerar kommandomönstret, vilket tillåter dig att skapa applikationer med kommandon och underkommandon (som git commit eller docker run).

Flagghantering: Både lokala och bestående flaggor med automatisk parsning och typomvandling.

Automatisk hjälp: Genererar hjälptext och användningsinformation automatiskt.

Intelligenta förslag: Ger förslag när användare skriver fel (“Menade du ‘status’?”).

Skalskomplettering: Genererar kompletteringsskript för bash, zsh, fish och PowerShell.

Flexibel utskrift: Fungerar smidigt med anpassade formaterare och utskriftsstilar.

Introduktion till Viper

Viper är en komplett konfigurationslösning för Go-applikationer, designad för att fungera smidigt med Cobra. Den hanterar konfiguration från flera källor med en tydlig prioriteringsordning.

Nyckelfunktioner i Viper

Flera konfigurationskällor:

  • Konfigurationsfiler (JSON, YAML, TOML, HCL, INI, envfile, Java properties)
  • Miljövariabler
  • Kommandoradsflaggor
  • Fjärrkonfigurationssystem (etcd, Consul)
  • Standardvärden

Konfigurationsprioritetsordning:

  1. Explicita anrop till Set
  2. Kommandoradsflaggor
  3. Miljövariabler
  4. Konfigurationsfil
  5. Nyckelvärdesförråd
  6. Standardvärden

Live-övervakning: Övervakar konfigurationsfiler och laddar om automatiskt när ändringar sker.

Typomvandling: Automatisk omvandling till olika Go-typer (sträng, int, bool, varaktighet, etc.).

Att börja: Installation

Först, initialisera en ny Go-modul och installera båda biblioteken:

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

Valfritt, installera Cobra CLI-genereraren för ramverk:

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

Bygg din första CLI-applikation

Låt oss bygga ett praktiskt exempel: ett uppgiftshanteringsverktyg med konfigurationsstöd.

Projektstruktur

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

Huvudinloppspunkt

// main.go
package main

import "mytasks/cmd"

func main() {
    cmd.Execute()
}

Rotkommando med 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: "Ett enkelt uppgiftshanteringsverktyg",
    Long: `MyTasks är ett CLI-uppgiftshanterare som hjälper dig organisera
dina dagliga uppgifter med lätthet. Byggt med Cobra och 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", "",
        "konfigurationsfil (standard är $HOME/.mytasks.yaml)")
    rootCmd.PersistentFlags().String("db",
        "databashanteringsfilens plats")

    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("Använder konfigurationsfil:", viper.ConfigFileUsed())
    }
}

Lägga till underkommandon

// cmd/add.go
package cmd

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

var priority string

var addCmd = &cobra.Command{
    Use:   "add [uppgiftsbeskrivning]",
    Short: "Lägg till en ny uppgift",
    Args:  cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        task := args[0]
        db := viper.GetString("database")

        fmt.Printf("Lägger till uppgift: %s\n", task)
        fmt.Printf("Prioritet: %s\n", priority)
        fmt.Printf("Databas: %s\n", db)

        // Här skulle du implementera faktisk uppgiftslagring
    },
}

func init() {
    rootCmd.AddCommand(addCmd)

    addCmd.Flags().StringVarP(&priority, "priority", "p", "medium",
        "Uppgiftsprioritet (låg, medium, hög)")
}

Lista-kommando

// 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: "Lista alla uppgifter",
    Run: func(cmd *cobra.Command, args []string) {
        db := viper.GetString("database")

        fmt.Printf("Listar uppgifter från: %s\n", db)
        fmt.Printf("Visa slutförda: %v\n", showCompleted)

        // Här skulle du implementera faktisk uppgiftslistning
    },
}

func init() {
    rootCmd.AddCommand(listCmd)

    listCmd.Flags().BoolVarP(&showCompleted, "completed", "c", false,
        "Visa slutförda uppgifter")
}

Implementera datalagring

För en produktionsklar uppgiftshanterings-CLI behöver du implementera faktisk datalagring. Medan vi använder en enkel databasvägkonfiguration här, har du flera alternativ för att spara data:

  • SQLite: Lättviktsdatabas utan server, perfekt för CLI-verktyg
  • PostgreSQL/MySQL: Fullfjädrade databaser för mer komplexa applikationer
  • JSON/YAML-filer: Enkel filbaserad lagring för lättviktsbehov
  • Inbyggda databaser: BoltDB, BadgerDB för nyckelvärdeslagring

Om du arbetar med relationella databaser som PostgreSQL, vill du använda ett ORM eller frågebyggare. För en omfattande jämförelse av Go-databibliotek, se vårt guide på Jämförelse av Go ORMs för PostgreSQL: GORM vs Ent vs Bun vs sqlc.

Avancerad konfiguration med Viper

Exempel på konfigurationsfil

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

notifications:
  enabled: true
  sound: true

priorities:
  high: red
  medium: yellow
  low: green

Läsning av inbäddad konfiguration

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

    fmt.Printf("Meddelanden aktiverade: %v\n", enabled)
    fmt.Printf("Ljud aktiverat: %v\n", sound)
}

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

Miljövariabler

Viper läser automatiskt miljövariabler med det konfigurerade prefixet:

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

Live-konfigurationsuppdatering

func watchConfig() {
    viper.WatchConfig()
    viper.OnConfigChange(func(e fsnotify.Event) {
        fmt.Println("Konfigurationsfil ändrad:", e.Name)
        // Ladda om konfigurationsberoende komponenter
    })
}

Bäst praxis

1. Använd Cobra Generator för konsistens

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

2. Organisera kommandon i separata filer

Håll varje kommando i sin egen fil under cmd/ katalogen för underhållbarhet.

3. Utnyttja bestående flaggor

Använd bestående flaggor för alternativ som gäller för alla underkommandon:

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

4. Implementera korrekt felhantering

var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "Min applikation",
    RunE: func(cmd *cobra.Command, args []string) error {
        if err := doSomething(); err != nil {
            return fmt.Errorf("misslyckades med att göra något: %w", err)
        }
        return nil
    },
}

5. Ge meningsfull hjälptext

var cmd = &cobra.Command{
    Use:   "deploy [miljö]",
    Short: "Distribuera applikation till specificerad miljö",
    Long: `Distribuerar byggs och distribuerar din applikation till den
specificerade miljön. Stödda miljöer är:
  - utveckling (dev)
  - staging
  - produktion (prod)`,
    Example: `  myapp deploy staging
  myapp deploy production --version=1.2.3`,
}

6. Ställ rimliga standardvärden

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

7. Validera konfiguration

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

Testning av CLI-applikationer

Testning av kommandon

// 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("kommandoutförande misslyckades: %v", err)
    }
}

Testning med olika konfigurationer

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

    // Kör dina tester

    viper.Reset() // Rensa upp
}

Generera Shell-kompletteringar

Cobra kan generera kompletteringsskript för olika skal:

// cmd/completion.go
package cmd

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

var completionCmd = &cobra.Command{
    Use:   "completion [bash|zsh|fish|powershell]",
    Short: "Generera kompletteringsskript",
    Long: `För att ladda kompletteringar:

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

Byggande och distribution

Bygg för flera plattformar

# 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

Minska binärstorlek

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

Lägg till versionsinformation

// 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: "Skriv ut versionsinformation",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Printf("Version: %s\n", Version)
        fmt.Printf("Commit: %s\n", Commit)
        fmt.Printf("Byggd: %s\n", BuildTime)
    },
}

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

Bygg med versionsinformation:

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

Verkliga exempel

Populära öppna källkodsverktyg byggda med Cobra och Viper:

  • kubectl: Kubernetes kommandoradsverktyg
  • Hugo: Statisk webbplatsgenerator
  • GitHub CLI (gh): GitHubs officiella CLI
  • Docker CLI: Behållarhantering
  • Helm: Kubernetes pakethanterare
  • Skaffold: Kubernetes utvecklingsflödesverktyg
  • Cobra CLI: Självhosting - Cobra använder sig själv!

Utöver traditionella DevOps-verktyg sträcker sig Gos CLI-möjligheter till AI och maskininlärningsapplikationer. Om du är intresserad av att bygga CLI-verktyg som interagerar med stora språkmodeller, kolla in vår guide om Begränsa LLMs med strukturerad output med hjälp av Ollama och Go, som visar hur man bygger Go-applikationer som arbetar med AI-modeller.

Användbara resurser

Slutsats

Cobra och Viper tillsammans ger en kraftfull grund för att bygga professionella CLI-applikationer i Go. Cobra hanterar kommandostrukturen, flaggparsing och hjälpgenerering, medan Viper hanterar konfiguration från flera källor med intelligent prioritering.

Denna kombination gör att du kan skapa CLI-verktyg som är:

  • Lätta att använda med intuitiva kommandon och automatisk hjälp
  • Flexibla med flera konfigurationskällor
  • Professionella med shell-kompletteringar och korrekt felhantering
  • Underhållbara med ren kodorganisation
  • Portabla över olika plattformar

Oavsett om du bygger utvecklarverktyg, systemverktyg eller DevOps-automatisering, ger Cobra och Viper den solida grund du behöver för att skapa CLI-applikationer som användare kommer att älska.