Написать CLI-утилиту на Go без боли: короткое руководство
Собираем рабочую CLI-утилиту на Go с подкомандами, флагами и автодополнением за один вечер. От go mod init до готового бинарника — пошагово, с проверками на каждом этапе.

Что получится и что понадобится
К концу руководства у вас будет утилита taskctl с двумя подкомандами — add и list, флагами, валидацией ввода и скомпилированным бинарником под вашу ОС. Времени — около часа, если Go уже стоит.
Требования до старта
- Go 1.21+ установлен,
go versionотвечает без ошибок; - терминал с доступом к файловой системе;
- текстовый редактор или IDE.
Мы используем библиотеку cobra — де-факто стандарт для CLI на Go (на ней построены kubectl, hugo, gh). Версия — github.com/spf13/cobra v1.8.x.
Шаг 1. Инициализация проекта
- Создайте каталог и перейдите в него:
mkdir taskctl && cd taskctl - Инициализируйте модуль:
Проверка: появился файлgo mod init taskctlgo.modс именем модуляtaskctl. - Установите cobra:
Проверка: вgo get github.com/spf13/cobra@latestgo.modпоявилась строкаrequire github.com/spf13/cobra.
Шаг 2. Корневая команда
Создайте файл main.go:
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
// rootCmd — точка входа, вызывается при запуске без подкоманд
var rootCmd = &cobra.Command{
Use: "taskctl",
Short: "Простой менеджер задач в терминале",
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Проверка: go run main.go --help выводит справку с описанием «Простой менеджер задач в терминале».
Шаг 3. Подкоманда add с флагами
Добавьте в тот же main.go перед функцией main:
var priority int
var addCmd = &cobra.Command{
Use: "add [название задачи]",
Short: "Добавить задачу",
Args: cobra.MinimumNArgs(1), // без аргумента — ошибка
Run: func(cmd *cobra.Command, args []string) {
title := args[0]
fmt.Printf("Задача добавлена: %s (приоритет %d)\n", title, priority)
},
}
func init() {
// Флаг --priority, по умолчанию 1
addCmd.Flags().IntVarP(&priority, "priority", "p", 1, "Приоритет от 1 до 5")
rootCmd.AddCommand(addCmd)
}
Проверка:
go run main.go add "Купить молоко" -p 3
# Задача добавлена: Купить молоко (приоритет 3)
Если запустить go run main.go add без аргумента, cobra сама выдаст ошибку — это заслуга cobra.MinimumNArgs(1).
Шаг 4. Подкоманда list
Для полноты картины добавим вывод. В реальном проекте данные хранились бы в файле или БД; здесь — заглушка, чтобы показать структуру:
var listCmd = &cobra.Command{
Use: "list",
Short: "Показать все задачи",
Run: func(cmd *cobra.Command, args []string) {
// Заглушка: в рабочем проекте — чтение из хранилища
fmt.Println("Задач пока нет. Добавьте первую: taskctl add \"текст\"")
},
}
func init() {
rootCmd.AddCommand(listCmd)
}
Проверка: go run main.go list выводит сообщение-заглушку, а go run main.go --help теперь показывает обе подкоманды.
Шаг 5. Валидация флагов
cobra проверяет количество аргументов, но логику значений флагов нужно добавить самому. Замените Run в addCmd на RunE — вариант, возвращающий ошибку:
RunE: func(cmd *cobra.Command, args []string) error {
if priority < 1 || priority > 5 {
return fmt.Errorf("приоритет должен быть от 1 до 5, получено: %d", priority)
}
fmt.Printf("Задача добавлена: %s (приоритет %d)\n", args[0], priority)
return nil
},
Проверка: go run main.go add "Тест" -p 9 возвращает ошибку с понятным текстом, а код выхода — не ноль.
Cobra отдельно обрабатывает ошибки парсинга флагов и ошибки бизнес-логики. RunE позволяет возвращать свои ошибки и при этом сохраняет автоматический вывод справки при неверном вызове.
Шаг 6. Сборка бинарника
go build -o taskctl .
./taskctl add "Отправить отчёт" -p 2
Размер бинарника — обычно 5–8 МБ. Если нужен минимум:
go build -ldflags="-s -w" -o taskctl .
Флаги -s -w убирают символы отладки и DWARF-информацию, экономя примерно 30 % объёма.
Проверка: ./taskctl --help работает без установленного Go — бинарник самодостаточен.
Если не получилось
| Ошибка | Причина | Решение |
|---|---|---|
cannot find module providing package github.com/spf13/cobra | Не выполнен go get или вы вне каталога модуля | Убедитесь, что стоите в папке с go.mod, и повторите go get |
rootCmd.AddCommand undefined | Несколько функций init() в одном файле конфликтуют | Go допускает несколько init() в одном файле, но лучше объедините их в одну |
flag redefined: priority | Флаг регистрируется дважды — обычно из-за дублирования init | Удалите дубль, оставьте одну регистрацию флага |
Бинарник не запускается: exec format error | Скомпилировано под другую ОС/архитектуру | Соберите без кросс-компиляции или задайте правильные GOOS и GOARCH |
Cobra — библиотека для построения CLI-приложений на Go. Предоставляет маршрутизацию подкоманд, парсинг флагов, генерацию справки и автодополнение для bash/zsh/fish.
Что делать дальше
- Добавьте хранение задач в JSON-файл через стандартный
encoding/json— это превратит заглушку в рабочий инструмент. - Сгенерируйте автодополнение:
rootCmd.GenBashCompletionFile("taskctl.bash")— точный синтаксис уточняйте в документации cobra для вашей версии. - Для конфигурационных файлов посмотрите на
github.com/spf13/viper— от тех же авторов, интегрируется с cobra. - Кросс-компиляция под Linux/macOS/Windows — одна строка: задайте
GOOSиGOARCHпередgo build.
Проверка грамотности: Орфография, пунктуация, стиль — с объяснением каждой правки
Спросить за 10 ₽Источники: Cobra — документация и репозиторий на GitHub, Go command-line tutorial — официальная документация Go
Частые вопросы
Обязательно ли использовать cobra или можно обойтись стандартной библиотекой?
Как добавить автодополнение в терминале?
А если у меня другая версия Go — 1.18 или 1.20?
Бинарник получается большим — 7 МБ для простой утилиты. Это нормально?
Как покрыть CLI-утилиту тестами?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.