Menu

Arguments de ligne de commande en Golang : os.Args, flag et variables d'environnement

Comment un programme Go lit sa ligne de commande : os.Args, le package flag pour des options typées, les sous-commandes avec FlagSet, les variables d'environnement avec os.Getenv et os.LookupEnv, et les codes de sortie avec os.Exit.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

os.Args

os.Args est une slice de chaînes. os.Args[0] est le nom du programme, le reste ce sont les arguments exactement tels que le shell les a transmis.

Dans le panneau Args de l'éditeur, chaque champ est un argument, transmis tel quel. Essayez hello, two words et -v dans trois champs : le programme voit trois arguments, et two words reste un seul argument avec un espace à l'intérieur. Dans un terminal, c'est le shell qui découpe, donc l'équivalent est go run . hello "two words" -v.

Vérifiez toujours len(os.Args) avant d'indexer. os.Args[1] sans argument provoque un panic index out of range [1] with length 1.

Les arguments sont des chaînes. Convertissez les nombres avec strconv.Atoi ou strconv.ParseFloat et gérez l'erreur, car les utilisateurs tapent n'importe quoi.

Le package flag

Pour des options comme -port 8080 -verbose, utilisez flag. Il analyse, convertit les types, signale les erreurs et génère un message d'aide.

Sans argument, ce programme affiche hello, world une fois. Dans le panneau Args, essayez -name et Gopher dans deux champs, puis -count=3, -loud et extra dans trois autres. Le programme affiche HELLO, GOPHER! trois fois et remaining args: [extra].

Comment fonctionnent les flags :

  • Chaque fonction de définition (flag.String, flag.Int, flag.Bool, flag.Float64, flag.Duration, flag.Uint64...) prend un nom, une valeur par défaut et un texte d'aide, et renvoie un pointeur. Lisez la valeur avec *name après flag.Parse().
  • Les formes Var se lient à une variable que vous avez déjà : flag.IntVar(&cfg.Port, "port", 8080, "port"). C'est plus propre pour une struct de configuration.
  • Les utilisateurs peuvent écrire -name value, -name=value, --name value ou --name=value. Go ne fait aucune différence entre un et deux tirets.
  • Les booléens ont besoin de = pour prendre une valeur. -loud met true, -loud=false met false, mais -loud false met true et laisse false comme argument positionnel.
  • L'analyse s'arrête au premier argument qui n'est pas un flag (ou à --). prog file.txt -v traite -v comme un argument positionnel. Mettez les flags en premier.
  • flag.Args() renvoie les arguments positionnels restants, flag.NArg() leur nombre, et flag.Arg(i) l'un d'entre eux.

Un flag inconnu ou une valeur invalide affiche une erreur suivie de l'aide, puis sort avec le statut 2. -h ou -help affiche l'aide et sort avec le statut 0 (depuis Go 1.15). Le texte d'aide est généré à partir de vos définitions :

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

Affectez une fonction à flag.Usage pour afficher votre propre en-tête avant d'appeler flag.PrintDefaults().

Sous-commandes avec FlagSet

Des outils comme git commit -m msg ont des sous-commandes avec leurs propres flags. Créez un flag.FlagSet par sous-commande et choisissez-en un avec un switch sur le premier argument :

flag.ContinueOnError fait renvoyer une erreur à Parse au lieu de quitter, ce qui garde la fonction testable. run reçoit les arguments en paramètre au lieu de lire os.Args, donc un test peut appeler run([]string{"list", "-all"}) directement. Essayez list et -all dans le panneau Args, ou delete pour voir le chemin d'erreur.

Pour les grosses CLI avec des commandes imbriquées, l'autocomplétion shell et une documentation générée, la plupart des projets utilisent la bibliothèque tierce github.com/spf13/cobra. Le package standard flag suffit bien pour les petits outils.

Variables d'environnement

  • os.Getenv renvoie "" aussi bien quand la variable est absente que quand elle vaut une chaîne vide. os.LookupEnv fait la différence.
  • Les valeurs sont toujours des chaînes. Convertissez-les et validez-les au démarrage, et échouez avec un message clair plutôt qu'au milieu d'une requête.
  • os.Setenv affecte le processus courant et les processus enfants lancés ensuite. Il ne peut pas modifier l'environnement du shell qui vous a lancé.
  • os.Environ() renvoie toutes les variables sous forme de chaînes "KEY=value".

Une organisation courante de la configuration : des flags pour ce qu'une personne tape, des variables d'environnement pour les réglages de déploiement (les plateformes de conteneurs les définissent), les flags l'emportant sur l'environnement et l'environnement sur les valeurs par défaut.

Codes de sortie et os.Exit

Un programme Go sort avec le statut 0 quand main retourne. os.Exit(code) termine le processus immédiatement avec ce statut. Par convention, 0 signifie succès, 1 une erreur générale et 2 une erreur d'utilisation (le package flag utilise 2).

os.Exit n'exécute pas les fonctions différées. Les fichiers ne sont pas vidés et le nettoyage par defer est sauté. log.Fatal appelle os.Exit(1) et a le même effet. Gardez os.Exit à un seul endroit, à la fin de main, et mettez le vrai programme dans une fonction run qui renvoie une erreur, comme dans l'exemple des sous-commandes :

func main() {
	if err := run(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, "error:", err)
		os.Exit(1)
	}
}

Écrivez les messages d'erreur sur os.Stderr, pas sur os.Stdout, pour qu'ils restent visibles quand la sortie est redirigée vers un fichier ou envoyée à une autre commande. Un panic non récupéré sort avec le statut 2.

Erreurs courantes

  • Indexer os.Args sans vérifier sa longueur. Les arguments manquants provoquent un panic.
  • Lire les flags avant flag.Parse(). Vous obtenez les valeurs par défaut.
  • Oublier le *. fmt.Println(port) affiche une adresse comme 0xc000012345, pas la valeur.
  • Mettre les flags après les arguments positionnels. prog input.txt -v n'analyse pas -v.
  • -verbose false pour un flag booléen. Écrivez -verbose=false.
  • Appeler os.Exit ou log.Fatal au fond du programme. Le nettoyage différé ne s'exécute jamais et le code ne peut pas être testé. Remontez les erreurs jusqu'à main.

Questions fréquentes

Comment récupérer les arguments de ligne de commande en Go ?

os.Args est un []string qui contient le nom du programme à l'indice 0 et les arguments ensuite. os.Args[1:] sont les arguments tapés par l'utilisateur. Vérifiez len(os.Args) avant d'indexer, sinon le programme provoque un panic quand un argument manque.

Comment utiliser le package flag en Go ?

Déclarez les flags, appelez flag.Parse(), puis lisez-les : port := flag.Int("port", 8080, "port to listen on"), flag.Parse(), fmt.Println(*port). Les fonctions renvoient des pointeurs. Les utilisateurs écrivent -port=9000, -port 9000 ou --port 9000, et -h affiche l'aide générée.

Comment lire une variable d'environnement en Go ?

os.Getenv("HOME") renvoie la valeur, ou une chaîne vide si la variable n'est pas définie. Pour distinguer une variable absente d'une variable vide, utilisez v, ok := os.LookupEnv("HOME"). os.Setenv modifie l'environnement du processus courant et des processus enfants qu'il lance ensuite.

os.Exit exécute-t-il les fonctions différées en Go ?

Non. os.Exit termine le processus immédiatement avec le code de statut donné, et les appels différés ne s'exécutent pas, donc une sortie en buffer peut être perdue et des fichiers peuvent ne pas être vidés. Un motif courant est func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) } }, avec tout le vrai travail et les defers dans run.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER