Aller au contenu
Développement medium

Écrire une CLI en Go avec Cobra

8 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 +700 guides gratuits, sans pub ni tracking. 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