Introduzione
Le utility da riga di comando sono raramente pronte all'uso senza ulteriore configurazione. Sulla maggior parte delle piattaforme, le utility della riga di comando accettano flag per personalizzare l'esecuzione del comando. I flag sono stringhe delimitate dal valore-chiave aggiunte dopo il nome del comando. Go consente di creare utilità da riga di comando che accettano flag utilizzando il pacchetto flag
dalla libreria standard.
In questo tutorial esplorerai vari modi per utilizzare il pacchetto flag
per creare diversi tipi di utility da riga di comando. Utilizzerai un flag per controllare l'output del programma, introdurre argomenti posizionali in cui mescoli flag e altri dati e quindi implementi i comandi secondari.
Usare una flag per cambiare il comportamento di un programma
L'utilizzo del pacchetto flag
prevede tre passaggi: in primo luogo, definire le variabili per acquisire i valori dei flag, quindi definire i flag che verranno utilizzati dall'applicazione Go e infine analizzare i flag forniti all'applicazione al momento dell'esecuzione. La maggior parte delle funzioni all'interno del pacchetto flag
riguardano la definizione di flag e l'associazione a variabili definite dall'utente. La fase di analisi è gestita dalla funzione Parse()
.
Creerai un programma che definisce un flag booleano che modifica il messaggio che verrà stampato sullo standard output. Se è presente una flag -color
, il programma stamperà un messaggio in blu. Se non viene fornita alcun flag, il messaggio verrà stampato senza alcun colore.
Crea un nuovo file chiamato boolean.go
:
nano boolean.go
Aggiungi il seguente codice al file per creare il programma:
In questo esempio vengono utilizzate le sequenze di escape ANSI per indicare al terminale di visualizzare un output colorato. Queste sono sequenze specializzate di caratteri, quindi ha senso definire un nuovo type per loro. In questo esempio, abbiamo chiamato quel tipo (type) Color
e definito il tipo (type) come string
. Definiamo quindi una tavola di colori (palette) da utilizzare nel blocco const
che segue. La funzione colorize
definita dopo il blocco const
accetta una di queste costanti Color
e una variabile string
per la colorazione del messaggio. Indica quindi al terminale di cambiare colore stampando prima la sequenza di escape per il colore richiesto, quindi stampa il messaggio e infine richiede che il terminale ripristini il suo colore stampando la speciale sequenza di ripristino del colore.
All'interno main
, usiamo la funzione flag.Bool
per definire un flag booleano chiamato color
. Il secondo parametro di questa funzione, false
, imposta il valore predefinito per questo flag quando non viene fornito. Contrariamente alle aspettative che potresti avere, impostando questo su true
non si inverte il comportamento in modo tale che fornire un flag lo farà diventare falso. Di conseguenza, il valore di questo parametro è quasi sempre false
con flag booleani.
Il parametro finale è una stringa di documentazione che può essere stampata come messaggio di utilizzo. Il valore restituito da questa funzione è un puntatore bool
. La funzione flag.Parse
sulla riga successiva utilizza questo puntatore per impostare la variabile di base bool
ai flag passati dall'utente. Siamo quindi in grado di controllare il valore di questo puntatore bool
dereferenziando il puntatore. Utilizzando questo valore booleano, possiamo quindi chiamare colorize
quando il flag -color
è impostato e chiamare la variabile fmt.Println
quando il flag è assente.
Salvare il file ed eseguire il programma senza flag:
go run boolean.go
Vedrai il seguente output:
Hello, Noviello!
Ora esegui di nuovo questo programma con il flag -color
:
go run boolean.go -color
L'output sarà lo stesso testo, ma questa volta nel colore blu.
I flag non sono gli unici valori passati ai comandi. È inoltre possibile inviare nomi di file o altri dati.
Lavorare con argomenti posizionali
In genere i comandi accettano una serie di argomenti che fungono da oggetto del focus del comando. Ad esempio, il comando head
, che stampa le prime righe di un file, viene spesso invocato come head example.txt
. Il file example.txt
è un argomento posizionale nell'invocazione del comando head
.
La funzione Parse()
continuerà ad analizzare i flag che incontra fino a quando non rileva un argomento non flag. Il pacchetto flag
li rende disponibili tramite le funzioni Args()
e Arg()
.
Per illustrare questo, costruirai una reimplementazione semplificata del comando head
, che visualizza le prime diverse righe di un determinato file:
Crea un nuovo file chiamato head.go
e aggiungi il seguente codice:
Innanzitutto, definiamo una variabile count
per contenere il numero di righe che il programma dovrebbe leggere dal file. Definiamo quindi il flag -n
usando flag.IntVar
, rispecchiando il comportamento del programma originale head
. Questa funzione ci consente di passare il nostro puntatore a una variabile in contrasto con le funzioni flag
che non hanno il suffisso Var
. A parte questa differenza, il resto dei parametri seguirà flag.IntVar
la sua controparte flag.Int
: il nome del flag, un valore predefinito e una descrizione. Come nell'esempio precedente, chiamiamo quindi flag.Parse()
per elaborare l'input dell'utente.
La sezione successiva legge il file. Definiamo innanzitutto una variabile io.Reader
che verrà impostata sul file richiesto dall'utente o input standard passato al programma. All'interno dell'istruzione if
, utilizziamo la funzione flag.Arg
per accedere al primo argomento posizionale dopo tutti i flag. Se l'utente ha fornito un nome file, questo verrà impostato. Altrimenti, sarà la stringa vuota (""
). Quando è presente un nome file, utilizziamo la funzione os.Open
per aprire quel file e impostare il file io.Reader
precedentemente definito su quel file. Altrimenti, usiamo os.Stdin
per leggere dallo standard input.
La sezione finale utilizza un *bufio.Scanner
creato con bufio.NewScanner
per leggere le righe io.Reader
dalla variabile in
. Esaminiamo fino al valore count
utilizzando un ciclo for
, chiamando break
se la scansione della linea buf.Scan
produce un valore false
, indicando che il numero di linee è inferiore al numero richiesto dall'utente.
Esegui questo programma e visualizza il contenuto del file che hai appena scritto usando head.go
come argomento del file:
go run head.go -- head.go
Il separatore --
è un flag speciale riconosciuto dal pacchetto flag
che indica che non seguono più argomenti flag. Quando si esegue questo comando, si riceve il seguente output:
package main
import (
"bufio"
"flag"
Usa il flag -n
che hai definito per regolare la quantità di output:
go run head.go -n 1 head.go
Questo produce solo l'istruzione del pacchetto:
package main
Infine, quando il programma rileva che non sono stati forniti argomenti posizionali, legge l'input dall'input standard, proprio come head
. Prova a eseguire questo comando:
echo "fish\nlobsters\nsharks\nminnows" | go run head.go -n 3
Vedrai l'output:
fish
lobsters
sharks
Il comportamento delle funzioni flag
che hai visto finora è stato limitato all'esame dell'intero richiamo del comando. Non sempre si desidera questo comportamento, soprattutto se si sta scrivendo uno strumento da riga di comando che supporta i comandi secondari.
Utilizzare il FlagSet per implementare i comandi secondari
Le moderne applicazioni da riga di comando spesso implementano "sotto-comandi" (sub-commands) per raggruppare una suite di strumenti in un unico comando. Lo strumento più noto che utilizza questo modello è git
. Quando si esamina un comando come git init
, git
è il comando ed init
è il sottocomando di git
. Una caratteristica notevole dei sotto-comandi è che ogni sotto-comando può avere la propria collezione di flag.
Le applicazioni Go possono supportare i comandi secondari con il proprio set di flag utilizzando il type flag.(*FlagSet)
. Per illustrare ciò, creare un programma che implementa un comando usando due sotto-comandi con flag diversi.
Crea un nuovo file chiamato subcommand.go
e aggiungi il seguente contenuto al file:
Questo programma è diviso in diverse parti: la funzione main
, la funzione root
e le singole funzioni per implementare il comando secondario. La funzione main
gestisce gli errori restituiti dai comandi. Se una funzione restituisce un errore, l'istruzione if
la rileva, stampa l'errore e il programma esce con un codice di stato di 1
, indicando che si è verificato un errore nel resto del sistema operativo. All'interno di main
, passiamo tutti gli argomenti con cui è stato invocato il programma root
. Rimuoviamo il primo argomento, che è il nome del programma (negli esempi precedenti ./subcommand
) tagliando per primo os.Args
.
La funzione root
definisce []Runner
dove verranno definiti tutti i sotto-comandi. Runner
è un'interfaccia per i comandi secondari che consente al root
di recuperare il nome del comando secondario utilizzando Name()
e confrontarlo con il contenuto della variabile subcommand
. Una volta individuato il comando secondario corretto dopo aver ripetuto la variabile cmds
, inizializziamo il comando secondario con il resto degli argomenti e invochiamo il metodo Run()
di quel comando.
Definiamo solo un sotto-comando, sebbene questo framework ci consenta facilmente di crearne altri. Con GreetCommand
viene creata un'istanza utilizzando NewGreetCommand
in cui creiamo un nuovo *flag.FlagSet
utilizzando flag.NewFlagSet
. flag.NewFlagSet
accetta due argomenti: un nome per il set di flag e una strategia per la segnalazione degli errori di analisi. Il nome *flag.FlagSet
è accessibile usando il metodo flag.(*FlagSet).Name
. Usiamo questo nel metodo (*GreetCommand).Name()
in modo che il nome del comando secondario corrisponda al nome che abbiamo dato a *flag.FlagSet
. NewGreetCommand
definisce anche un flag -name
in modo analogo agli esempi precedenti, ma richiede invece ciò come metodo fuori dal campo *flag.FlagSet
di *GreetCommand
, gc.fs
. Quando root
chiama il metodo Init()
di *GreetCommand
, passiamo gli argomenti forniti al metodo Parse
del campo *flag.FlagSet
.
Sarà più semplice visualizzare i sotto-comandi se si crea questo programma e quindi lo si esegue. Costruisci il programma:
go build subcommand.go
Ora esegui il programma senza argomenti:
./subcommand
Vedrai questo output:
You must pass a sub-command
Ora esegui il programma con il comando secondario greet
:
./subcommand greet
Questo produce il seguente output:
Hello World !
Ora usa il flag -name
con greet
per specificare un nome:
./subcommand greet -name Noviello
Vedrai questo output dal programma:
Hello Noviello !
Questo esempio illustra alcuni principi alla base di come le più grandi applicazioni della riga di comando potrebbero essere strutturate in Go. I FlagSet
sono progettati per fornire agli sviluppatori un maggiore controllo su dove e come i flag vengono elaborati dalla logica di analisi dei flag.
Conclusione
I flag rendono le tue applicazioni più utili in più contesti perché offrono ai tuoi utenti il controllo su come vengono eseguiti i programmi. È importante fornire agli utenti utili impostazioni predefinite, ma dovresti dare loro l'opportunità di ignorare le impostazioni che non funzionano per la loro situazione. Hai visto che il pacchetto flag
offre opzioni flessibili per presentare le opzioni di configurazione ai tuoi utenti. Puoi scegliere alcuni semplici flag o creare una suite estensibile di sotto-comandi.