os.Args
os.Args è uno slice di stringhe. os.Args[0] è il nome del programma, gli altri sono gli argomenti esattamente come li ha passati la shell.
Nel pannello Args dell'editor, ogni campo è un argomento e viene passato così com'è. Prova hello, two words e -v in tre campi: il programma vede tre argomenti, e two words resta un solo argomento con uno spazio all'interno. In un terminale è la shell a fare la suddivisione, quindi l'equivalente è go run . hello "two words" -v.
Controlla sempre len(os.Args) prima di accedere a un indice. os.Args[1] senza argomenti va in panic con index out of range [1] with length 1.
Gli argomenti sono stringhe. Converti i numeri con strconv.Atoi o strconv.ParseFloat e gestisci l'errore, perché gli utenti digitano di tutto.
Il package flag
Per opzioni come -port 8080 -verbose, usa flag. Fa il parsing, converte i tipi, segnala gli errori e genera un messaggio di aiuto.
Senza argomenti stampa hello, world una volta. Nel pannello Args, prova -name e Gopher in due campi, poi -count=3, -loud ed extra in altri tre. Il programma stampa HELLO, GOPHER! tre volte e remaining args: [extra].
Come funzionano i flag:
- Ogni funzione di definizione (
flag.String,flag.Int,flag.Bool,flag.Float64,flag.Duration,flag.Uint64...) riceve un nome, un valore predefinito e una stringa d'uso, e restituisce un puntatore. Leggi il valore con*namedopoflag.Parse(). - Le forme
Varsi legano a una variabile che hai già:flag.IntVar(&cfg.Port, "port", 8080, "port"). È più ordinato per una struct di configurazione. - Gli utenti possono scrivere
-name value,-name=value,--name valueo--name=value. Go non fa differenza tra uno e due trattini. - I booleani hanno bisogno di
=per ricevere un valore.-loudimposta true,-loud=falseimposta false, ma-loud falseimposta true e lasciafalsecome argomento posizionale. - Il parsing si ferma al primo argomento che non è un flag (o a
--).prog file.txt -vtratta-vcome argomento posizionale. Metti prima i flag. flag.Args()restituisce gli argomenti posizionali rimasti,flag.NArg()il loro numero eflag.Arg(i)uno di essi.
Un flag sconosciuto o un valore non valido stampa un errore insieme all'uso ed esce con stato 2. -h o -help stampa l'uso ed esce con stato 0 (da Go 1.15). Il testo d'uso viene generato dalle tue definizioni:
Usage of greet:
-count int
how many times (default 1)
-delay duration
pause between greetings, e.g. 10ms
-loud
shout the greeting
-name string
who to greet (default "world")
Assegna una funzione a flag.Usage per stampare una tua intestazione prima di chiamare flag.PrintDefaults().
Sottocomandi con FlagSet
Strumenti come git commit -m msg hanno sottocomandi con flag propri. Crea un flag.FlagSet per ogni sottocomando e scegline uno con uno switch sul primo argomento:
flag.ContinueOnError fa sì che Parse restituisca un errore invece di terminare il programma, il che mantiene la funzione testabile. run riceve gli argomenti come parametro invece di leggere os.Args, quindi un test può chiamare direttamente run([]string{"list", "-all"}). Prova list e -all nel pannello Args, oppure delete per vedere il percorso di errore.
Per CLI grandi con comandi annidati, completamento nella shell e documentazione generata, la maggior parte dei progetti usa il package di terze parti github.com/spf13/cobra. Il package standard flag copre bene gli strumenti piccoli.
Variabili d'ambiente
os.Getenvrestituisce""sia quando la variabile manca sia quando è impostata a una stringa vuota.os.LookupEnvdistingue i due casi.- I valori sono sempre stringhe. Convertili e validali all'avvio, e fallisci con un messaggio chiaro invece che a metà di una richiesta.
os.Setenvinfluisce sul processo corrente e sui processi figli avviati dopo. Non può cambiare l'ambiente della shell che ti ha avviato.os.Environ()restituisce tutte le variabili come stringhe"KEY=value".
Un'organizzazione comune della configurazione: flag per le cose che digita una persona, variabili d'ambiente per le impostazioni di deploy (le impostano le piattaforme di container), con i flag che hanno la precedenza sull'ambiente e l'ambiente sui valori predefiniti.
Codici di uscita e os.Exit
Un programma Go esce con stato 0 quando main ritorna. os.Exit(code) termina subito il processo con quello stato. Per convenzione 0 significa successo, 1 un errore generico e 2 un errore d'uso (il package flag usa 2).
os.Exit non esegue le funzioni differite. I file non vengono scritti su disco e la pulizia con defer viene saltata. log.Fatal chiama os.Exit(1) e ha lo stesso effetto. Tieni os.Exit in un solo punto, alla fine di main, e metti il programma vero in una funzione run che restituisce un errore, come fa l'esempio con i sottocomandi:
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
Scrivi i messaggi di errore su os.Stderr, non su os.Stdout, così restano visibili quando l'output viene rediretto su un file o passato con una pipe a un altro comando. Un panic non recuperato esce con stato 2.
Errori comuni
- Accedere a un indice di
os.Argssenza controllarne la lunghezza. Gli argomenti mancanti provocano un panic. - Leggere i flag prima di
flag.Parse(). Ottieni i valori predefiniti. - Dimenticare l'
*.fmt.Println(port)stampa un indirizzo come0xc000012345, non il valore. - Mettere i flag dopo gli argomenti posizionali.
prog input.txt -vnon analizza-v. -verbose falseper un flag booleano. Scrivi-verbose=false.- Chiamare
os.Exitolog.Fatalin profondità nel programma. La pulizia differita non viene mai eseguita e il codice non si può testare. Fai risalire gli errori fino amain.
Domande frequenti
Come ottengo gli argomenti da riga di comando in Go?
os.Args è uno []string che contiene il nome del programma all'indice 0 e gli argomenti dopo di esso. os.Args[1:] sono gli argomenti digitati dall'utente. Controlla len(os.Args) prima di accedere a un indice, altrimenti il programma va in panic quando manca un argomento.
Come si usa il package flag in Go?
Dichiara i flag, chiama flag.Parse(), poi leggili: port := flag.Int("port", 8080, "port to listen on"), flag.Parse(), fmt.Println(*port). Le funzioni restituiscono puntatori. Gli utenti scrivono -port=9000, -port 9000 o --port 9000, e -h stampa l'uso generato automaticamente.
Come leggo una variabile d'ambiente in Go?
os.Getenv("HOME") restituisce il valore, o una stringa vuota se la variabile non è impostata. Per distinguere una variabile non impostata da una impostata a vuoto, usa v, ok := os.LookupEnv("HOME"). os.Setenv modifica l'ambiente del processo corrente e dei processi figli che avvia in seguito.
os.Exit esegue le funzioni differite in Go?
No. os.Exit termina subito il processo con il codice di stato indicato e le chiamate differite non vengono eseguite, quindi l'output nel buffer può andare perso e i file possono non essere scritti su disco. Uno schema comune è func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) } }, con tutto il lavoro vero e i defer dentro run.