CLI на cobra и viper
Системное программирование в Go — отдельный пласт между «скриптом на Python» и «микросервисом». cobra строит дерево команд и флагов, viper объединяет конфиг из файлов и env. Статья дополняет модули и embed и обработку ошибок.
См. также: Первая программа · Простые приложения · Важные интерфейсы — io.Reader.
Зачем не только flag
Пакет flag из stdlib достаточен для одной команды. Для утилит с подкомандами (git commit, kubectl get), алиасами и автодокументацией берут spf13/cobra. Конфиг из YAML/JSON/env — spf13/viper. Так же устроены kubectl, hugo, docker compose.
| Задача | stdlib | cobra + viper |
|---|---|---|
| Один бинарник, 2 флага | flag | Избыточно |
| Подкоманды insert/list/delete | Вручную | cobra |
| Файл config + env override | Вручную | viper |
| Shell completion | Нет | cobra |
Каркас cobra
var rootCmd = &cobra.Command{
Use: "phonebook",
Short: "Телефонная книга в CLI",
}
func main() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
Подкоманда:
var insertCmd = &cobra.Command{
Use: "insert [name] [phone]",
Short: "Добавить контакт",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
name, phone := args[0], args[1]
return store.Insert(name, phone)
},
}
func init() {
rootCmd.AddCommand(insertCmd)
insertCmd.Flags().StringVarP(&configPath, "config", "c", "phonebook.yaml", "путь к конфигу")
}
Разбор:
RunEвозвращаетerror— cobra печатает её и выставляет exit code.Args: cobra.ExactArgs(2)валидирует аргументы до вызоваRunE.- Флаги регистрируют на конкретной команде; глобальные — на
rootCmd.PersistentFlags().
viper — конфигурация
func initConfig() error {
viper.SetConfigName("phonebook")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("$HOME/.phonebook")
viper.AutomaticEnv()
viper.SetEnvPrefix("PHONEBOOK")
return viper.ReadInConfig()
}
# phonebook.yaml
data_file: ./contacts.json
log_level: info
path := viper.GetString("data_file")
level := viper.GetString("log_level")
Разбор:
- Порядок приоритета (типичный): явные флаги > env > файл > defaults.
PHONEBOOK_DATA_FILEмапится на ключdata_fileприSetEnvPrefix.ReadInConfigможет вернуть ошибку «файл не найден» — иногда это нормально, тогда задают defaults черезviper.SetDefault.
Сериализация контактов — через encoding/json. Viper читает формат файла; доменные структуры по-прежнему unmarshaling в Go-типы.
embed и статика в бинарнике
//go:embed default.yaml
var defaultConfig []byte
func loadDefaults() {
viper.SetConfigType("yaml")
_ = viper.ReadConfig(bytes.NewReader(defaultConfig))
}
Разбор:
- Встроенный конфиг даёт работоспособный бинарник «из коробки»; пользовательский файл переопределяет значения.
- Подробнее — 29.md.
stdin, stdout, stderr
CLI-утилиты следуют UNIX-конвенции:
| Поток | Назначение |
|---|---|
| stdout | Результат для pipe (phonebook list | jq) |
| stderr | Логи и ошибки |
| stdin | Данные pipe (cat names | phonebook import) |
data, err := io.ReadAll(os.Stdin)
Разбор:
Сигналы и graceful exit
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := rootCmd.ExecuteContext(ctx); err != nil {
os.Exit(1)
}
Разбор:
ExecuteContextпередаёт отмену вRunE— долгие операции проверяютctx.Done().- На Windows
syscall.SIGTERMможет отсутствовать; для кроссплатформенных утилит достаточноos.Interrupt.
Пример дерева команд (phone book)
| Команда | Действие |
|---|---|
phonebook insert NAME PHONE | добавить |
phonebook search NAME | найти |
phonebook list | вывести все |
phonebook delete NAME | удалить |
Персистентность — JSON-файл; позже тот же домен уходит в веб-сервис и REST.
Тестирование CLI
func TestInsertCmd(t *testing.T) {
cmd := insertCmd
cmd.SetArgs([]string{"Ann", "+1-555-0100"})
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
err := cmd.Execute()
if err != nil {
t.Fatal(err)
}
}
Разбор:
SetArgsимитирует argv без subprocess.- Store подменяют интерфейсом или temp-файлом — см. 192.md.
Ключевые тезисы
- cobra — дерево команд, валидация args, help и completion.
- viper — единая точка конфигурации (файл + env + флаги).
- stdout/stderr разделяют данные и диагностику.
signal.NotifyContextдаёт корректное завершение по Ctrl+C.
Мини-практикум
- Добавьте подкоманду
listс флагом--format=json|table. - Подключите viper: defaults в embed, override через
~/.phonebook/config.yaml. - Напишите тест
Execute()сSetArgsдля happy-path и неверного числа аргументов.
Типичные ошибки
| Ошибка | Что делать |
|---|---|
| Логи в stdout | Перенести в stderr / slog |
Один giant main() | Разнести по cmd/ и internal/ |
| Viper в каждой функции | Обёртка Config с явными полями |
Игнор RunE error | Всегда return err наверх |
Связанные материалы
- Модули, workspace, embed и slog
- Профилирование и fuzz — если CLI стал узким местом
- Веб на stdlib — эволюция phone book в HTTP-сервис
- gRPC в Go — внутренние RPC после REST
Базовый разбор HTTP — HTTP как основа веб-интеграций.
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). Основы языка Go - философия простоты, модель компиляции и идиоматичный подход к системной разработке. Go — это статически типизированный язык программирования общего назначения, разработанный компанией Google для создания эффективных, масштабируемых и надежных систем. Набор советов, правил, принципов и обычаев в разработке на этом языке. История Go - инженерные цели языка, философия простоты и эволюция инструментов экосистемы. Экосистема приложений на Go - встроенные инструменты, workflow разработки и практики сопровождения проектов. Кавычки, rune и string, точка, запятая, автоматическая вставка точки с запятой, скобки, подчёркивания и типичные ошибки новичков в Go. Предопределённые идентификаторы не являются ключевыми словами, но имеют специальное значение в языке. Их можно переопределить в локальной области видимости, но делать это не рекомендуется. Набор функций, которые включены в стандартную библиотеку языка. Особенности Go - интерфейсы, композиция, модель ошибок и практики написания поддерживаемого кода. Go вводит конкурентность через встроенные синтаксические конструкции и правила выполнения. Ниже рассматриваются основные направления практического применения Go, объяснённые через призму его технических характеристик и требований реальных инфраструктур. Типизация, набор правил определения типа данных значений языка.Основы языка Go
Что требуется знать перед началом изучения языка программирования Go
Рекомендации по разработке на Go
История языка Go
Экосистема приложений на Go
Синтаксис и пунктуация в Go
Ключевые слова языка Go
Встроенные функции и пакеты Go
Особенности языка Go
Синтаксические конструкции Go
Области применения Go
Типы данных и объявление переменных в Go