Direct naar de hoofdinhoud
Module 10 · Les 4 van 549/50 in de cursus~18 min
Modulelessen (4/5)

Miniproject: een CLI met vlaggen

Laten we de bouwstenen van de vorige modules samenvoegen om een ​​kleine idiomatische CLI te bouwen: vlagparsing met het pakket flag, afhandeling van positionele argumenten, fouten op os.Stderr en de juiste exitcode.

Het flag pakket

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

Belangrijkste punten:

  • flag.Int, flag.String, flag.Bool, flag.Duration (enz.) geven een pointer terug: je gebruikt *n, *upper.
  • flag.Parse() moet EENMAAL worden aangeroepen, voordat de waarden worden gelezen en vóór flag.Args().
  • flag.Args() retourneert de positionele argumenten (die na -- of na de laatste vlag).
  • Geaccepteerde formulieren: -n 3, -n=3, --n=3. Booleaanse waarden: -u (waar), -u=false.

Conventionele exitcodes

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

Shells en scripts verwachten deze codes: gebruik ze consequent. Nooit verlaat een niet-hoofdgoroutine met os.Exit: elke defer wordt overgeslagen, inclusief log doorspoelen.

Stderr versus Stdout

Oude en heilige Unix-regel:

  • os.Stdout → nuttige uitvoer, geschikt voor pijpleidingen (cli | grep ...).
  • os.Stderr → diagnostiek, fouten, voortgangsbalken, gebruik.
Go
fmt.Println("result: 42")                   // stdout
fmt.Fprintln(os.Stderr, "error: ...")       // stderr

Op deze manier toont cli 2>/dev/null alleen de resultaten, stuurt cli >out.txt alleen het resultaat om en blijven fouten zichtbaar.

log versus fmt

  • fmt voor gebruikersuitvoer.
  • log voor diagnostiek: voegt een tijdstempel toe, schrijft standaard naar os.Stderr, heeft log.Fatal (logs + os.Exit(1)) en log.Panic.
Go
log.SetFlags(log.LstdFlags | log.Lshortfile) // timestamp + file:line
log.Fatal("boom") // prints and exits with 1

Gebruik voor moderne apps (Go 1.21+) log/slog voor gestructureerde logboekregistratie (JSON of tekst, niveaus, getypte velden).

Architectuur: scheid main van logica

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
}

Voordelen:

  • Testbaar: u geeft bytes.Buffer door als stdout/stderr en verifieert de uitvoer.
  • Geen globale status: elke FlagSet is geïsoleerd.
  • main blijft klein: het bereidt IO + afgevaardigden voor.

Subopdrachten

Voor CLI's met subopdrachten (mycli serve, mycli migrate), een schema zonder externe afhankelijkheden:

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

Elk subcommando heeft zijn eigen flag.NewFlagSet. Voor grotere projecten structureren bibliotheken zoals cobra of urfave/cli dit schema met automatisch gegenereerde hulp.

Oefeningen

Oefening#go.m10.l4.e1
Pogingen: 0Laden…

Definieer de vlag -n deze tip is met standaard waarde 1 en beschrijft 'ripetizioni', poi chiama flag.Parse() en stempel de waarde.

Editor laden…

Oplossing beschikbaar na 3 pogingen

Oefening#go.m10.l4.e2
Pogingen: 0Laden…

Als er geen argument is dat in de dopo-vlag staat.Parse, stempel een 'manca-argoment' op de volgende stap en beëindig het met os.Exit(1).

Editor laden…

Oplossing beschikbaar na 3 pogingen

Quiz#go.m10.l4.e3
Klaar

Zijn er berichten over fouten en gebruik in een CLI Unix-vriendelijk?

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