Menu

Argomenti da riga di comando in Golang: os.Args, flag e variabili d'ambiente

Come un programma Go legge la sua riga di comando: os.Args, il package flag per le opzioni tipizzate, i sottocomandi con FlagSet, le variabili d'ambiente con os.Getenv e os.LookupEnv e i codici di uscita con os.Exit.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

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 *name dopo flag.Parse().
  • Le forme Var si 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 value o --name=value. Go non fa differenza tra uno e due trattini.
  • I booleani hanno bisogno di = per ricevere un valore. -loud imposta true, -loud=false imposta false, ma -loud false imposta true e lascia false come argomento posizionale.
  • Il parsing si ferma al primo argomento che non è un flag (o a --). prog file.txt -v tratta -v come argomento posizionale. Metti prima i flag.
  • flag.Args() restituisce gli argomenti posizionali rimasti, flag.NArg() il loro numero e flag.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.Getenv restituisce "" sia quando la variabile manca sia quando è impostata a una stringa vuota. os.LookupEnv distingue 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.Setenv influisce 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.Args senza 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 come 0xc000012345, non il valore.
  • Mettere i flag dopo gli argomenti posizionali. prog input.txt -v non analizza -v.
  • -verbose false per un flag booleano. Scrivi -verbose=false.
  • Chiamare os.Exit o log.Fatal in profondità nel programma. La pulizia differita non viene mai eseguita e il codice non si può testare. Fai risalire gli errori fino a main.

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.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA