Direkt zum Hauptinhalt springen
eLearner.app
Modul 10 · Lektion 4 von 549/50 im Kurs~18 min
Lektionen des Moduls (4/5)

Miniprojekt: ein CLI mit Flags

Lassen Sie uns die Bausteine ​​der vorherigen Module zusammenfügen, um eine kleine idiomatische CLI zu erstellen: Flag-Parsing mit dem flag-Paket, Behandlung von Positionsargumenten, Fehler bei os.Stderr und der richtige Exit-Code.

Das flag-Paket

Go
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    n := flag.Int("n", 1, "number of repetitions")
    upper := flag.Bool("u", false, "print in uppercase")
    flag.Parse()

    args := flag.Args() // remaining positional arguments
    if len(args) < 1 {
        fmt.Fprintln(os.Stderr, "usage: echo [-n N] [-u] <text>")
        os.Exit(2)
    }

    text := args[0]
    if *upper {
        text = strings.ToUpper(text)
    }
    for i := 0; i < *n; i++ {
        fmt.Println(text)
    }
}

Kernpunkte:

  • flag.Int, flag.String, flag.Bool, flag.Duration (usw.) geben einen Zeiger zurück: Sie verwenden *n, *upper.
  • flag.Parse() muss EINMAL aufgerufen werden, vor dem Lesen der Werte und vor flag.Args().
  • flag.Args() gibt die positionalen Argumente zurück (diejenigen nach -- oder nach dem letzten Flag).
  • Akzeptierte Formen: flag.String0, flag.String1, flag.String2. Boolesche Werte: flag.String3 (wahr), flag.String4.

Konventionelle Exit-Codes

Go
os.Exit(0) // success
os.Exit(1) // generic error
os.Exit(2) // usage error (incorrect flag usage)

Shells und Skripte erwarten diese Codes: Verwenden Sie sie konsequent. Niemals eine Nicht-Haupt-Goroutine mit os.Exit verlassen: Es wird jedes defer übersprungen, einschließlich der log-Leerung.

Stdderr vs Stdout

Alte und heilige Unix-Regel:

  • os.Stdout → nützliche Ausgabe, weiterleitungsfähig (cli | grep ...).
  • os.Stderr → Diagnose, Fehler, Fortschrittsbalken, Nutzung.
Go
fmt.Println("result: 42")                   // stdout
fmt.Fprintln(os.Stderr, "error: ...")       // stderr

Auf diese Weise zeigt cli 2>/dev/null nur die Ergebnisse an, cli >out.txt leitet nur das Ergebnis um und Fehler bleiben sichtbar.

log vs. fmt

  • fmt für Benutzerausgabe.
  • log für die Diagnose: Fügt einen Zeitstempel hinzu, schreibt standardmäßig in os.Stderr, verfügt über log.Fatal (Protokolle + os.Exit(1)) und log.Panic.
Go
log.SetFlags(log.LstdFlags | log.Lshortfile) // timestamp + file:line
log.Fatal("boom") // prints and exits with 1

Verwenden Sie für moderne Apps (Go 1.21+) log/slog für die strukturierte Protokollierung (JSON oder Text, Ebenen, typisierte Felder).

Architektur: main von der Logik trennen

Go
func main() {
    if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func run(args []string, stdout, stderr io.Writer) error {
    // here use a new flag.FlagSet instead of the global flag.CommandLine
    fs := flag.NewFlagSet("echo", flag.ContinueOnError)
    fs.SetOutput(stderr)
    n := fs.Int("n", 1, "repetitions")
    if err := fs.Parse(args); err != nil {
        return err
    }
    // ...
    return nil
}

Vorteile:

  • Testbar: Sie übergeben bytes.Buffer als stdout/stderr und überprüfen die Ausgabe.
  • Kein globaler Status: Jeder FlagSet ist isoliert.
  • main bleibt winzig: Es bereitet IO + Delegierte vor.

Unterbefehle

Für CLIs mit Unterbefehlen (mycli serve, mycli migrate) ein Schema ohne externe Abhängigkeiten:

Go
func main() {
    if len(os.Args) < 2 {
        usage()
        os.Exit(2)
    }
    switch os.Args[1] {
    case "serve":
        serveCmd(os.Args[2:])
    case "migrate":
        migrateCmd(os.Args[2:])
    default:
        usage()
        os.Exit(2)
    }
}

Jeder Unterbefehl hat seinen eigenen flag.NewFlagSet. Bei größeren Projekten strukturieren Bibliotheken wie cobra oder urfave/cli dieses Schema mit automatisch generierter Hilfe.

Übungen

Übung#go.m10.l4.e1
Versuche: 0Wird geladen…

Definieren Sie das Flag -n vom Typ int mit dem Wert von default 1 und beschreiben Sie es als 'Reparatur', dann verwenden Sie flag.Parse() und geben Sie den Wert ein.

Editor wird geladen…

Lösung nach 3 Versuchen verfügbar

Übung#go.m10.l4.e2
Versuche: 0Wird geladen…

Es ist nicht möglich, dass ein argomentales Argument für flag.Parse angezeigt wird, drücken Sie „Argument manuieren“ auf Stderr und beenden Sie es mit os.Exit(1).

Editor wird geladen…

Lösung nach 3 Versuchen verfügbar

Quiz#go.m10.l4.e3
Bereit

Warum sendet der Stream Fehlermeldungen zu Fehlern und der Verwendung in einer Unix-freundlichen CLI?

Go
fmt.Fprintln(???, "uso: prog ...")
Antwortoptionen