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
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óórflag.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
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.
fmt.Println("result: 42") // stdout
fmt.Fprintln(os.Stderr, "error: ...") // stderrOp 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
fmtvoor gebruikersuitvoer.logvoor diagnostiek: voegt een tijdstempel toe, schrijft standaard naaros.Stderr, heeftlog.Fatal(logs +os.Exit(1)) enlog.Panic.
log.SetFlags(log.LstdFlags | log.Lshortfile) // timestamp + file:line
log.Fatal("boom") // prints and exits with 1Gebruik voor moderne apps (Go 1.21+) log/slog voor gestructureerde logboekregistratie (JSON of tekst, niveaus, getypte velden).
Architectuur: scheid main van logica
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.Bufferdoor als stdout/stderr en verifieert de uitvoer. - Geen globale status: elke
FlagSetis geïsoleerd. mainblijft klein: het bereidt IO + afgevaardigden voor.
Subopdrachten
Voor CLI's met subopdrachten (mycli serve, mycli migrate), een schema zonder externe afhankelijkheden:
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
Definieer de vlag -n deze tip is met standaard waarde 1 en beschrijft 'ripetizioni', poi chiama flag.Parse() en stempel de waarde.
Oplossing beschikbaar na 3 pogingen
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).
Oplossing beschikbaar na 3 pogingen
Zijn er berichten over fouten en gebruik in een CLI Unix-vriendelijk?
fmt.Fprintln(???, "uso: prog ...")