Salt la conținutul principal
Modulul 10 · Lecția 4 din 549/50 în curs~18 min
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

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

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 de flag.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

Go
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.
Go
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

  • fmt pentru ieșirea utilizatorului.
  • log pentru diagnosticare: adaugă un marcaj de timp, scrie implicit în os.Stderr, are log.Fatal (jurnal + os.Exit(1)) și PROTECTEDINLINE_34__.
Go
log.SetFlags(log.LstdFlags | log.Lshortfile) // timestamp + file:line
log.Fatal("boom") // prints and exits with 1

Pentru 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ă

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
}

Avantaje:

  • Testabil: treceți bytes.Buffer ca stdout/stderr și verificați rezultatul.
  • Fără stare globală: fiecare FlagSet este izolat.
  • main ră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:

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

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

Exercițiu#go.m10.l4.e1
Încercări: 0Se încarcă…

Definisci il flag -n di tip int con valore di default 1 și descriere 'ripetizioni', poi chiama flag.Parse() și stampa il valore.

Se încarcă editorul...

Soluție disponibilă după 3 încercări

Exercițiu#go.m10.l4.e2
Încercări: 0Se încarcă…

Se non c'è almeno un argomento posizionale dopo flag.Parse, stampa 'manca argomento' su Stderr e termina con os.Exit(1).

Se încarcă editorul...

Soluție disponibilă după 3 încercări

Quiz#go.m10.l4.e3
Gata

Care stream stampi mesaje de erori și utilizare într-un CLI Unix-friendly?

Go
fmt.Fprintln(???, "uso: prog ...")
Opțiuni de răspuns