Aller au contenu
English
English
Développement medium

Écrire une CLI en Go avec Cobra

20 min de lecture

Go

Dès qu'un outil en ligne de commande dépasse une poignée d'options, gérer à la main le parsing des arguments, l'aide et les sous-commandes devient ingérable. Cobra est la bibliothèque qui structure tout cela : c'est elle qui propulse kubectl, docker ou gh. Ce guide construit un outil scan avec une commande racine, deux sous-commandes et des flags, puis montre l'aide et l'autocomplétion générées. Public visé : intermédiaire en Go. Tous les exemples sont testés avec Go 1.26 et Cobra 1.10.

  • Initialiser une commande racine Cobra et l'exécuter.
  • Ajouter des sous-commandes et une arborescence.
  • Distinguer flags persistants et flags locaux.
  • Générer l'aide et l'autocomplétion.

Cobra est une dépendance de votre module, épinglée dans go.mod :

Fenêtre de terminal
go mod init example/scan
go get github.com/spf13/cobra@v1.10.2

Tout part d'une commande racine de type *cobra.Command. Son champ Use donne le nom de l'outil, Short le résumé affiché dans l'aide. La méthode Execute() lance le parsing.

main.go
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var verbose bool
func main() {
root := &cobra.Command{
Use: "scan",
Short: "scan inspecte des cibles locales et distantes",
}
// Flag persistant : hérité par toutes les sous-commandes.
root.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "sortie détaillée")
if err := root.Execute(); err != nil {
os.Exit(1)
}
}

Une sous-commande est elle-même un *cobra.Command, rattachée à la racine par AddCommand. Sa logique vit dans RunE, la variante de Run qui renvoie une erreur : Cobra l'affiche et positionne le code de sortie. Ajoutons deux sous-commandes, files et ports, chacune avec ses flags locaux.

main.go
var path string
filesCmd := &cobra.Command{
Use: "files",
Short: "Compte les entrées d'un répertoire",
RunE: func(cmd *cobra.Command, args []string) error {
entries, err := os.ReadDir(path)
if err != nil {
return err
}
if verbose {
fmt.Fprintf(os.Stderr, "lecture de %s\n", path)
}
fmt.Printf("%d entrées dans %s\n", len(entries), path)
return nil
},
}
filesCmd.Flags().StringVar(&path, "path", ".", "répertoire à inspecter")
var host string
var top int
portsCmd := &cobra.Command{
Use: "ports",
Short: "Liste les ports courants à tester sur un hôte",
RunE: func(cmd *cobra.Command, args []string) error {
common := []int{22, 80, 443, 5432, 6379, 8080, 9090}
if top > len(common) {
top = len(common)
}
fmt.Printf("%d ports courants pour %s : %v\n", top, host, common[:top])
return nil
},
}
portsCmd.Flags().StringVar(&host, "host", "localhost", "hôte cible")
portsCmd.Flags().IntVar(&top, "top", 3, "nombre de ports à afficher")
root.AddCommand(filesCmd, portsCmd)

La distinction est importante. Un flag persistant, déclaré sur PersistentFlags(), est disponible sur la commande et toutes ses sous-commandes : --verbose fonctionne partout. Un flag local, déclaré sur Flags(), n'existe que pour sa commande : --path n'a de sens que pour files.

TypeDéclarationPortée
PersistantPersistentFlags()La commande et toute sa descendance
LocalFlags()La commande seule

Compilez, puis exécutez les sous-commandes. L'aide est générée automatiquement à partir des champs Short et des flags déclarés :

Fenêtre de terminal
go run . --help
scan inspecte des cibles locales et distantes
Usage:
scan [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
files Compte les entrées d'un répertoire
help Help about any command
ports Liste les ports courants à tester sur un hôte

Chaque sous-commande fait son travail, avec ses flags :

Fenêtre de terminal
go run . files --path /etc
89 entrées dans /etc
Fenêtre de terminal
go run . ports --host db.internal --top 4
4 ports courants pour db.internal : [22 80 443 5432]

Cobra a géré le routage vers la bonne sous-commande, la conversion du flag --top en entier et la valeur par défaut de --host.

Cobra ajoute d'office une commande completion qui produit le script d'autocomplétion pour bash, zsh, fish ou PowerShell. L'utilisateur l'installe une fois, et la tabulation complète alors commandes et flags :

Fenêtre de terminal
# Pour zsh, le temps de la session
source <(scan completion zsh)
  • Utilisez RunE, pas Run : renvoyer l'erreur laisse Cobra gérer l'affichage et le code de sortie.
  • Un flag persistant pour le transverse (--verbose, --config), un flag local pour le spécifique.
  • Gardez main mince : la logique métier va dans des paquets, la commande ne fait que l'appeler.
  • Épinglez la version de Cobra dans go.mod pour un build reproductible.
  • Laissez Cobra générer l'aide : ne réécrivez pas à la main ce que les champs Short/Long produisent.
  • Cobra structure une CLI en un arbre de *cobra.Command, comme kubectl ou docker.
  • AddCommand rattache les sous-commandes ; RunE porte leur logique et remonte les erreurs.
  • Flags persistants (hérités) et flags locaux (propres) couvrent les deux besoins.
  • L'aide et l'autocomplétion sont générées, pas écrites à la main.
  • Couplé à Viper, Cobra unifie flags, environnement et fichier de config.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens ce site gratuitement, sans publicité, sans profilage et sans compte à créer. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn