Lecții din modul (4/5)
Mini-proiect: un CLI cu steaguri
Să punem împreună blocurile de construcție ale modulelor anterioare pentru a construi un mic CLI idiomatic: analiza de semnalizare cu pachetul flag, gestionarea argumentelor poziționale, erori pe os.Stderr și codul de ieșire corect.
Pachetul flag
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)
}
}Puncte cheie:
flag.Int,flag.String,flag.Bool,flag.Duration(etc.) returnează un pointer: utilizați*n, _IN_LINE_8__.flag.Parse()trebuie apelat O dată, înainte de a citi valorile și înainte deflag.Args().flag.Args()returnează argumentele poziționale (cele după--sau după ultimul flag).- Formulare acceptate:
-n 3,-n=3,--n=3. Booleeni:-u(adevărat),-u=false.
Codurile de ieșire convenționale
os.Exit(0) // success
os.Exit(1) // generic error
os.Exit(2) // usage error (incorrect flag usage)Shell-urile și scripturile se așteaptă la aceste coduri: utilizați-le în mod constant. Nu ieși dintr-o rutină non-principală cu os.Exit: omite fiecare defer, inclusiv log spălare.
Stderr vs Stdout
Veche și sacră regulă Unix:
os.Stdout→ ieșire utilă, care poate fi conectată (cli | grep ...).os.Stderr→ diagnosticare, erori, bare de progres, utilizare.
fmt.Println("result: 42") // stdout
fmt.Fprintln(os.Stderr, "error: ...") // stderrÎn acest fel cli 2>/dev/null arată doar rezultatele, cli >out.txt redirecționează doar rezultatul, iar erorile rămân vizibile.
log vs fmt
fmtpentru ieșirea utilizatorului.logpentru diagnosticare: adaugă un marcaj de timp, scrie implicit înos.Stderr, arelog.Fatal(jurnal +os.Exit(1)) și PROTECTEDINLINE_34__.
log.SetFlags(log.LstdFlags | log.Lshortfile) // timestamp + file:line
log.Fatal("boom") // prints and exits with 1Pentru aplicațiile moderne (Go 1.21+) utilizați log/slog pentru înregistrarea structurată (JSON sau text, niveluri, câmpuri tastate).
Arhitectură: separați main de logică
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
}Avantaje:
- Testabil: treceți
bytes.Bufferca stdout/stderr și verificați rezultatul. - Fără stare globală: fiecare
FlagSeteste izolat. mainrămâne mic: pregătește IO + delegați.
Subcomenzi
Pentru CLI cu subcomenzi (mycli serve, mycli migrate), o schemă fără dependențe externe:
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)
}
}Fiecare subcomandă are propriul flag.NewFlagSet. Pentru proiecte mai mari, biblioteci precum cobra sau urfave/cli structurează această schemă cu ajutorul generat automat.
Exerciții
Definisci il flag -n di tip int con valore di default 1 și descriere 'ripetizioni', poi chiama flag.Parse() și stampa il valore.
Soluție disponibilă după 3 încercări
Se non c'è almeno un argomento posizionale dopo flag.Parse, stampa 'manca argomento' su Stderr e termina con os.Exit(1).
Soluție disponibilă după 3 încercări
Care stream stampi mesaje de erori și utilizare într-un CLI Unix-friendly?
fmt.Fprintln(???, "uso: prog ...")