Создание CLI-приложений на Go с использованием Cobra и Viper
Разработка CLI на Go с фреймворками Cobra и Viper
Интерфейсы командной строки (CLI) являются важными инструментами для разработчиков, системных администраторов и специалистов DevOps. Два библиотеки Go стали де-факто стандартом для разработки CLI на Go: Cobra для структуры команд и Viper для управления конфигурацией.
Go стал отличным языком для создания инструментов CLI благодаря своей производительности, простому развертыванию и кроссплатформенной поддержке.

Почему стоит выбирать Go для приложений CLI
Go предлагает убедительные преимущества для разработки CLI:
- Единый бинарный файл: Нет необходимости в среде выполнения или менеджерах пакетов
- Быстрое выполнение: Нативная компиляция обеспечивает отличную производительность
- Кроссплатформенная поддержка: Легкая компиляция для Linux, macOS, Windows и других платформ
- Богатая стандартная библиотека: Инструменты для работы с файлами, сетью и текстовыми данными
- Конкуренция: Встроенные горутины для параллельных операций
- Статическая типизация: Обнаружение ошибок на этапе компиляции
Популярные инструменты CLI, созданные на Go, включают Docker, Kubernetes (kubectl), Hugo, Terraform и GitHub CLI. Если вы новичок в Go или вам нужна быстрая справка, ознакомьтесь с нашим Go Cheat Sheet для основного синтаксиса и шаблонов.
Введение в Cobra
Cobra - это библиотека, предоставляющая простой интерфейс для создания мощных современных приложений CLI. Созданная Стивом Францией (spf13), тем же автором, что и Hugo и Viper, Cobra используется во многих популярных проектах на Go.
Основные возможности Cobra
Структура команд: Cobra реализует шаблон команд, позволяя создавать приложения с командами и подкомандами (как git commit или docker run).
Обработка флагов: Локальные и постоянные флаги с автоматическим разбором и преобразованием типов.
Автоматическая справка: Генерирует текст справки и информацию об использовании автоматически.
Интеллектуальные подсказки: Предоставляет подсказки при опечатках (“Вы имели в виду ‘status’?”).
Автодополнение оболочки: Генерация скриптов автодополнения для bash, zsh, fish и PowerShell.
Гибкий вывод: Работает плавно с пользовательскими форматировщиками и стилями вывода.
Введение в Viper
Viper - это полное решение для конфигурации приложений на Go, специально разработанное для работы с Cobra. Оно обрабатывает конфигурацию из нескольких источников с четким порядком приоритета.
Основные возможности Viper
Множественные источники конфигурации:
- Конфигурационные файлы (JSON, YAML, TOML, HCL, INI, envfile, Java properties)
- Переменные окружения
- Флаги командной строки
- Удаленные системы конфигурации (etcd, Consul)
- Значения по умолчанию
Порядок приоритета конфигурации:
- Явные вызовы Set
- Флаги командной строки
- Переменные окружения
- Конфигурационный файл
- Хранилище ключ-значение
- Значения по умолчанию
Мониторинг изменений: Отслеживание конфигурационных файлов и автоматическая перезагрузка при изменениях.
Преобразование типов: Автоматическое преобразование в различные типы Go (строка, int, bool, duration и т.д.).
Начало работы: Установка
Сначала инициализируйте новый модуль Go и установите обе библиотеки:
go mod init myapp
go get -u github.com/spf13/cobra@latest
go get -u github.com/spf13/viper@latest
По желанию установите генератор CLI Cobra для создания шаблонов:
go install github.com/spf13/cobra-cli@latest
Создание первого приложения CLI
Давайте создадим практический пример: инструмент управления задачами CLI с поддержкой конфигурации.
Структура проекта
mytasks/
├── cmd/
│ ├── root.go
│ ├── add.go
│ ├── list.go
│ └── complete.go
├── config/
│ └── config.go
├── main.go
└── config.yaml
Основная точка входа
// main.go
package main
import "mytasks/cmd"
func main() {
cmd.Execute()
}
Корневая команда с интеграцией Viper
// cmd/root.go
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
var rootCmd = &cobra.Command{
Use: "mytasks",
Short: "Простое CLI для управления задачами",
Long: `MyTasks - это CLI менеджер задач, который помогает вам организовывать
ежедневные задачи с легкостью. Создано с использованием Cobra и Viper.`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "",
"конфигурационный файл (по умолчанию $HOME/.mytasks.yaml)")
rootCmd.PersistentFlags().String("db",
"расположение файла базы данных")
viper.BindPFlag("database", rootCmd.PersistentFlags().Lookup("db"))
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
viper.AddConfigPath(home)
viper.AddConfigPath(".")
viper.SetConfigType("yaml")
viper.SetConfigName(".mytasks")
}
viper.SetEnvPrefix("MYTASKS")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Используется конфигурационный файл:", viper.ConfigFileUsed())
}
}
Добавление подкоманд
// cmd/add.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var priority string
var addCmd = &cobra.Command{
Use: "add [описание задачи]",
Short: "Добавить новую задачу",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
task := args[0]
db := viper.GetString("database")
fmt.Printf("Добавление задачи: %s\n", task)
fmt.Printf("Приоритет: %s\n", priority)
fmt.Printf("База данных: %s\n", db)
// Здесь вы бы реализовали фактическое сохранение задачи
},
}
func init() {
rootCmd.AddCommand(addCmd)
addCmd.Flags().StringVarP(&priority, "priority", "p", "medium",
"Приоритет задачи (низкий, средний, высокий)")
}
Команда списка
// cmd/list.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var showCompleted bool
var listCmd = &cobra.Command{
Use: "list",
Short: "Показать все задачи",
Run: func(cmd *cobra.Command, args []string) {
db := viper.GetString("database")
fmt.Printf("Показ задач из: %s\n", db)
fmt.Printf("Показывать выполненные: %v\n", showCompleted)
// Здесь вы бы реализовали фактический вывод списка задач
},
}
func init() {
rootCmd.AddCommand(listCmd)
listCmd.Flags().BoolVarP(&showCompleted, "completed", "c", false,
"Показывать выполненные задачи")
}
Реализация сохранения данных
Для производственного инструмента управления задачами CLI вам нужно будет реализовать фактическое сохранение данных. Хотя мы используем простой путь к базе данных здесь, у вас есть несколько вариантов для сохранения данных:
- SQLite: Легковесная, серверная база данных, идеальная для инструментов CLI
- PostgreSQL/MySQL: Полнофункциональные базы данных для более сложных приложений
- Файлы JSON/YAML: Простое файловое хранилище для легковесных нужд
- Встроенные базы данных: BoltDB, BadgerDB для хранения ключ-значение
Если вы работаете с реляционными базами данных, такими как PostgreSQL, вам захочется использовать ORM или конструктор запросов. Для всестороннего сравнения библиотек баз данных Go, ознакомьтесь с нашим руководством Сравнение Go ORMs для PostgreSQL: GORM vs Ent vs Bun vs sqlc.
Расширенная конфигурация с Viper
Пример конфигурационного файла
# .mytasks.yaml
database: ~/.mytasks.db
format: table
colors: true
notifications:
enabled: true
sound: true
priorities:
high: red
medium: yellow
low: green
Чтение вложенной конфигурации
func getNotificationSettings() {
enabled := viper.GetBool("notifications.enabled")
sound := viper.GetBool("notifications.sound")
fmt.Printf("Уведомления включены: %v\n", enabled)
fmt.Printf("Звук включен: %v\n", sound)
}
func getPriorityColors() map[string]string {
return viper.GetStringMapString("priorities")
}
Переменные окружения
Viper автоматически считывает переменные окружения с заданным префиксом:
export MYTASKS_DATABASE=/tmp/tasks.db
export MYTASKS_NOTIFICATIONS_ENABLED=false
mytasks list
Живая перезагрузка конфигурации
func watchConfig() {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Конфигурационный файл изменен:", e.Name)
// Перезагрузите компоненты, зависящие от конфигурации
})
}
Лучшие практики
1. Используйте генератор Cobra для согласованности
cobra-cli init
cobra-cli add serve
cobra-cli add create
2. Организуйте команды в отдельных файлах
Храните каждую команду в своем файле в директории cmd/ для удобства поддержки.
3. Используйте постоянные флаги
Используйте постоянные флаги для опций, которые применяются ко всем подкомандам:
rootCmd.PersistentFlags().StringP("output", "o", "text",
"Формат вывода (текст, json, yaml)")
4. Реализуйте правильную обработку ошибок
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "Мое приложение",
RunE: func(cmd *cobra.Command, args []string) error {
if err := doSomething(); err != nil {
return fmt.Errorf("не удалось выполнить что-то: %w", err)
}
return nil
},
}
5. Предоставляйте осмысленную справку
var cmd = &cobra.Command{
Use: "deploy [environment]",
Short: "Развертывание приложения в указанной среде",
Long: `Развертывание собирает и развертывает ваше приложение в
указанной среде. Поддерживаемые среды:
- development (dev)
- staging
- production (prod)`,
Example: ` myapp deploy staging
myapp deploy production --version=1.2.3`,
}
6. Устанавливайте разумные значения по умолчанию
func init() {
viper.SetDefault("port", 8080)
viper.SetDefault("timeout", "30s")
viper.SetDefault("retries", 3)
}
7. Проверяйте конфигурацию
func validateConfig() error {
port := viper.GetInt("port")
if port < 1024 || port > 65535 {
return fmt.Errorf("недопустимый порт: %d", port)
}
return nil
}
Тестирование CLI Приложений
Тестирование Команд
// cmd/root_test.go
package cmd
import (
"bytes"
"testing"
)
func TestRootCommand(t *testing.T) {
cmd := rootCmd
b := bytes.NewBufferString("")
cmd.SetOut(b)
cmd.SetArgs([]string{"--help"})
if err := cmd.Execute(); err != nil {
t.Fatalf("command execution failed: %v", err)
}
}
Тестирование с Разными Конфигурациями
func TestWithConfig(t *testing.T) {
viper.Set("database", "/tmp/test.db")
viper.Set("debug", true)
// Запустите свои тесты
viper.Reset() // Очистка
}
Генерация Автодополнений для Shell
Cobra может генерировать скрипты автодополнения для различных оболочек:
// cmd/completion.go
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Генерация скрипта автодополнения",
Long: `Для загрузки автодополнений:
Bash:
$ source <(mytasks completion bash)
Zsh:
$ source <(mytasks completion zsh)
Fish:
$ mytasks completion fish | source
PowerShell:
PS> mytasks completion powershell | Out-String | Invoke-Expression
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "bash":
cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
}
},
}
func init() {
rootCmd.AddCommand(completionCmd)
}
Сборка и Распространение
Сборка для Разных Платформ
# Linux
GOOS=linux GOARCH=amd64 go build -o mytasks-linux-amd64
# macOS
GOOS=darwin GOARCH=amd64 go build -o mytasks-darwin-amd64
GOOS=darwin GOARCH=arm64 go build -o mytasks-darwin-arm64
# Windows
GOOS=windows GOARCH=amd64 go build -o mytasks-windows-amd64.exe
Уменьшение Размера Бинарного Файла
go build -ldflags="-s -w" -o mytasks
Добавление Информации о Версии
// cmd/version.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var (
Version = "dev"
Commit = "none"
BuildTime = "unknown"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "Вывод информации о версии",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Версия: %s\n", Version)
fmt.Printf("Коммит: %s\n", Commit)
fmt.Printf("Собран: %s\n", BuildTime)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}
Сборка с информацией о версии:
go build -ldflags="-X 'mytasks/cmd.Version=1.0.0' \
-X 'mytasks/cmd.Commit=$(git rev-parse HEAD)' \
-X 'mytasks/cmd.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" \
-o mytasks
Реальные Примеры
Популярные открытые инструменты, созданные с использованием Cobra и Viper:
- kubectl: Инструмент командной строки Kubernetes
- Hugo: Генератор статических сайтов
- GitHub CLI (gh): Официальный CLI GitHub
- Docker CLI: Управление контейнерами
- Helm: Менеджер пакетов Kubernetes
- Skaffold: Инструмент рабочего процесса разработки Kubernetes
- Cobra CLI: Самохозяин - Cobra использует себя!
Помимо традиционных инструментов DevOps, возможности CLI на Go распространяются на приложения искусственного интеллекта и машинного обучения. Если вас интересует создание CLI-инструментов, взаимодействующих с большими языковыми моделями, ознакомьтесь с нашим руководством Ограничение LLMs с помощью структурированного вывода с использованием Ollama и Go, которое показывает, как создавать приложения на Go, работающие с моделями ИИ.
Полезные Ресурсы
- Cobra GitHub Репозиторий
- Viper GitHub Репозиторий
- Руководство пользователя Cobra
- Документация Viper
- Лучшие практики создания CLI-приложений на Go
Заключение
Cobra и Viper вместе предоставляют мощную основу для создания профессиональных CLI-приложений на Go. Cobra управляет структурой команд, разбором флагов и генерацией справки, в то время как Viper управляет конфигурацией из нескольких источников с интеллектуальным приоритетом.
Эта комбинация позволяет создавать CLI-инструменты, которые:
- Легки в использовании с интуитивными командами и автоматической справкой
- Гибкие с несколькими источниками конфигурации
- Профессиональные с автодополнением оболочки и правильной обработкой ошибок
- Легко поддерживаемые с чистой организацией кода
- Переносимые на разные платформы
Будь то создание инструментов для разработчиков, системных утилит или автоматизации DevOps, Cobra и Viper предоставляют прочную основу, необходимую для создания CLI-приложений, которые пользователи будут любить.