Работа с флагами командной строки в Go: пошаговая инструкция
Разберём пакет flag из стандартной библиотеки Go — от простейшего ключа до подкоманд. К концу инструкции у вас будет CLI-утилита, которая принимает строковые, числовые и булевы флаги, валидирует ввод и выводит справку.

Что нужно до начала
Инструкция написана для Go 1.21+. Всё, что описано ниже, работает и в более ранних версиях (пакет flag стабилен с Go 1.0), но примеры проверены на 1.21 и 1.22.
- Установленный Go — проверьте командой
go versionв терминале. - Любой текстовый редактор. VS Code с расширением Go или GoLand — удобнее, но не обязательно.
- Пустая директория для проекта.
Времени потребуется около 20 минут. Результат — рабочая утилита, которая считывает флаги, показывает справку по -h и корректно обрабатывает ошибки ввода.
Шаг 1. Создаём модуль и файл
Откройте терминал в пустой директории и выполните:
mkdir flagdemo && cd flagdemo
go mod init flagdemo
Создайте файл main.go. Пока оставьте его пустым — содержимое добавим на следующем шаге.
Проверка: в директории должны лежать два файла — go.mod и main.go. Команда cat go.mod покажет строку module flagdemo.
Шаг 2. Первый флаг — строковый
Вставьте в main.go следующий код целиком:
package main
import (
"flag"
"fmt"
)
func main() {
// Строковый флаг: имя, значение по умолчанию, описание
name := flag.String("name", "world", "Имя для приветствия")
flag.Parse()
fmt.Printf("Привет, %s!\n", *name)
}
Запустите:
go run main.go -name=Gopher
Проверка: в терминале появится Привет, Gopher!. Если запустить без флага — Привет, world!.
Функция flag.String возвращает указатель на строку, поэтому при использовании нужна разыменовка через *name. Это единственная неочевидность пакета — дальше к ней привыкнете.
Шаг 3. Добавляем числовой и булев флаги
Замените содержимое main.go:
package main
import (
"flag"
"fmt"
)
func main() {
name := flag.String("name", "world", "Имя для приветствия")
repeat := flag.Int("repeat", 1, "Сколько раз повторить приветствие")
shout := flag.Bool("shout", false, "Вывести в верхнем регистре")
flag.Parse()
for i := 0; i < *repeat; i++ {
msg := fmt.Sprintf("Привет, %s!", *name)
if *shout {
// Для ASCII достаточно strings.ToUpper,
// но для кириллицы тоже работает корректно
fmt.Println(toUpper(msg))
} else {
fmt.Println(msg)
}
}
}
func toUpper(s string) string {
// strings.ToUpper корректно обрабатывает UTF-8
import не нужен заново — добавьте "strings" в блок import
}
Стоп — в предыдущем фрагменте ошибка намеренно не допущена. Вот полный рабочий вариант:
package main
import (
"flag"
"fmt"
"strings"
)
func main() {
name := flag.String("name", "world", "Имя для приветствия")
repeat := flag.Int("repeat", 1, "Сколько раз повторить приветствие")
shout := flag.Bool("shout", false, "Вывести в верхнем регистре")
flag.Parse()
for i := 0; i < *repeat; i++ {
msg := fmt.Sprintf("Привет, %s!", *name)
if *shout {
msg = strings.ToUpper(msg)
}
fmt.Println(msg)
}
}
Запустите:
go run main.go -name=Go -repeat=3 -shout
Проверка: три строки ПРИВЕТ, GO!. Булев флаг не требует значения — его наличие означает true. Можно и явно: -shout=true.
Шаг 4. Привязка к переменным вместо указателей
Если разыменовка указателей раздражает, используйте вариант с Var-функциями. Они записывают значение прямо в вашу переменную:
package main
import (
"flag"
"fmt"
"strings"
)
func main() {
var name string
var repeat int
var shout bool
flag.StringVar(&name, "name", "world", "Имя для приветствия")
flag.IntVar(&repeat, "repeat", 1, "Сколько раз повторить")
flag.BoolVar(&shout, "shout", false, "Верхний регистр")
flag.Parse()
for i := 0; i < repeat; i++ {
msg := fmt.Sprintf("Привет, %s!", name)
if shout {
msg = strings.ToUpper(msg)
}
fmt.Println(msg)
}
}
Проверка: поведение идентично шагу 3, но в коде нет ни одной звёздочки.
Когда что использовать
| Способ | Возвращает | Когда удобнее |
|---|---|---|
flag.String, flag.Int, flag.Bool | Указатель | Короткие скрипты, мало флагов — объявление в одну строку |
flag.StringVar, flag.IntVar, flag.BoolVar | Ничего (пишет в переменную) | Структуры конфигурации, много флагов — удобнее читать без разыменовки |
Шаг 5. Позиционные аргументы после флагов
Пакет flag разделяет флаги и остальное по первому аргументу без дефиса. Всё, что осталось после флагов, доступно через flag.Args():
flag.Parse()
files := flag.Args() // срез строк — всё, что не является флагом
fmt.Println("Файлы:", files)
Вызов go run main.go -name=Go -- file1.txt file2.txt положит в files два элемента. Двойной дефис -- явно отделяет флаги от аргументов — полезно, если аргумент начинается с минуса.
Проверка: fmt.Println(flag.NArg()) после Parse покажет количество позиционных аргументов.
Шаг 6. Кастомная справка
По умолчанию -h или -help печатает все зарегистрированные флаги с описаниями. Если хотите изменить формат — переопределите flag.Usage:
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(),
"Использование: flagdemo [флаги] [файлы...]\n\nФлаги:\n")
flag.PrintDefaults()
}
Поместите этот блок до вызова flag.Parse().
Проверка: go run main.go -h выведет вашу строку и список флагов.
Шаг 7. Обработка ошибок ввода
Что произойдёт, если пользователь передаст -repeat=abc? По умолчанию программа напечатает ошибку, справку и завершится с кодом 2. Это поведение задаётся через flag.CommandLine.ErrorHandling, но для CLI-утилиты дефолт подходит в большинстве случаев. Менять его стоит только в тестах или библиотечном коде.
Пакетflagпокрывает большинство типовых случаев парсинга аргументов. Остальное — вложенные подкоманды, автодополнение, POSIX-стиль--long-flag— требует сторонних библиотек. Но начинать с них не стоит: сначала убедитесь, что стандартный пакет вам действительно мал.
Если не получилось
Пять ошибок, которые встречаются чаще всего:
flag provided but not defined: -name— вы вызвалиflag.Parse()до того, как зарегистрировали флаг-name. Перенесите всеflag.String/flag.StringVarвышеParse.invalid value "abc" for flag -repeat: parse error— пользователь передал строку в числовой флаг. Это штатное поведение, программа корректно завершится с ошибкой. Если нужна своя реакция — используйтеflag.NewFlagSetс режимомflag.ContinueOnError.- Значение флага не применяется, всегда дефолт — забыли вызвать
flag.Parse(). Без него значения из командной строки не считываются. - Флаг принимает следующий флаг как значение — пробел между именем и значением работает (
-name Gopher), но если значение начинается с дефиса, пакет решит, что это другой флаг. Используйте форму через=:-name=-test. flag redefined: name— один и тот же флаг зарегистрирован дважды. Убедитесь, что нет дублей вinit()иmain().
Что делать дальше
Стандартный пакет flag не поддерживает длинные флаги с двойным дефисом (--name), подкоманды в стиле git commit и автодополнение в shell. Если это нужно — посмотрите на сторонние библиотеки: cobra и urfave/cli — две самые популярные. Но перед их подключением задайте вопрос: вашей утилите действительно нужны подкоманды, или хватит пяти флагов?
Следующий практический шаг — вынести парсинг флагов в отдельную структуру Config и покрыть её тестами через flag.NewFlagSet. Это позволит запускать Parse с произвольным набором аргументов, не трогая os.Args. Тема подкоманд и FlagSet — отдельный разговор, который выходит за рамки этой инструкции.
Версии: примеры проверены на Go 1.21 и 1.22. Пакет flag стабилен с Go 1.0 и не менялся по API, поэтому код будет работать и на более старых версиях.
Проектная работа: От проблемы до защиты: продукт, этапы, вопросы комиссии
Спросить за 15 ₽Источники: Документация пакета flag — стандартная библиотека Go
Частые вопросы
Чем -name=value отличается от -name value?
Поддерживает ли стандартный пакет flag двойной дефис --name?
Что если пользователь передал неизвестный флаг?
У меня другая версия Go — код будет работать?
Можно ли использовать flag для парсинга переменных окружения?
Как передать флаг со значением, которое начинается с дефиса?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.