cobra/pflag для Go: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Библиотека cobra/pflag для Go: мощные флаги командной строки

В мире разработки на Go создание удобных и гибких интерфейсов командной строки (CLI) является одной из ключевых задач. Стандартная библиотека flag предоставляет базовые возможности для парсинга флагов, но её функционал ограничен. На помощь приходит cobra/pflag — мощная библиотека, которая расширяет возможности стандартного пакета и является неотъемлемой частью экосистемы Cobra (популярного фреймворка для создания CLI-приложений).

Основное преимущество pflag перед стандартным flag заключается в поддержке стилей флагов как POSIX/GNU (длинные флаги с двойным дефисом, например --verbose), так и BSD (короткие флаги с одним дефисом, например -v). Библиотека также поддерживает объединение коротких флагов (-abc вместо -a -b -c), флаги с обязательными аргументами, флаги-счётчики и многое другое. pflag полностью совместим с интерфейсом flag.Value, что позволяет легко мигрировать существующий код.

Библиотека активно используется в таких известных проектах, как Kubernetes, Helm, Hugo и многих других. Если вы планируете создавать серьёзное CLI-приложение на Go, pflag станет незаменимым инструментом, предоставляя гибкость, производительность и соответствие современным стандартам оформления командной строки.

Установка

Установка библиотеки производится стандартным для Go способом через менеджер пакетов go get:

go get github.com/spf13/pflag

После выполнения этой команды библиотека будет добавлена в ваш модуль Go (файл go.mod). Теперь вы можете импортировать её в свой код:

import "github.com/spf13/pflag"

Быстрый старт

Давайте создадим простое приложение, которое принимает имя пользователя и флаг отображения приветствия:

package main

import (
    "fmt"
    "github.com/spf13/pflag"
)

func main() {
    // Определяем флаги
    name := pflag.String("name", "World", "имя для приветствия")
    verbose := pflag.BoolP("verbose", "v", false, "подробный вывод")
    
    // Парсим флаги
    pflag.Parse()
    
    // Используем значения
    if *verbose {
        fmt.Printf("Режим: подробный\n")
        fmt.Printf("Имя: %s\n", *name)
    }
    
    fmt.Printf("Привет, %s!\n", *name)
}

Запустите программу с различными флагами:

go run main.go
go run main.go --name=Go
go run main.go -n Go -v
go run main.go --verbose --name "Go Developer"

Основные методы и функции

1. pflag.String(name, shorthand, value, usage)

Сигнатура: func String(name string, shorthand string, value string, usage string) *string

Создаёт флаг строкового типа с указанным именем, короткой формой, значением по умолчанию и описанием. Возвращает указатель на строку.

host := pflag.StringP("host", "H", "localhost", "адрес сервера")
pflag.Parse()
fmt.Printf("Подключаемся к %s\n", *host)

2. pflag.Int(name, shorthand, value, usage)

Сигнатура: func Int(name string, shorthand string, value int, usage string) *int

Создаёт флаг целочисленного типа. Полезен для указания портов, лимитов и других числовых параметров.

port := pflag.IntP("port", "p", 8080, "порт для прослушивания")
pflag.Parse()
fmt.Printf("Сервер запущен на порту %d\n", *port)

3. pflag.Bool(name, shorthand, value, usage)

Сигнатура: func Bool(name string, shorthand string, value bool, usage string) *bool

Создаёт флаг булевого типа. Флаги-переключатели, которые не требуют значения (простое присутствие в командной строке устанавливает true).

debug := pflag.BoolP("debug", "d", false, "включить режим отладки")
pflag.Parse()
if *debug {
    fmt.Println("Отладка включена")
}

4. pflag.StringSlice(name, shorthand, value, usage)

Сигнатура: func StringSlice(name string, shorthand string, value []string, usage string) *[]string

Создаёт флаг, который может принимать несколько строковых значений. Позволяет указывать один и тот же флаг несколько раз или передавать значения через запятую.

tags := pflag.StringSliceP("tag", "t", []string{}, "теги для фильтрации")
pflag.Parse()
fmt.Printf("Теги: %v\n", *tags)
// Использование: --tag=go --tag=cli или --tag=go,cli

5. pflag.IntSlice(name, shorthand, value, usage)

Сигнатура: func IntSlice(name string, shorthand string, value []int, usage string) *[]int

Аналог StringSlice для целых чисел. Позволяет передавать несколько числовых значений.

ids := pflag.IntSliceP("id", "i", []int{}, "идентификаторы для обработки")
pflag.Parse()
fmt.Printf("ID: %v\n", *ids)
// Использование: --id=1 --id=2 или --id=1,2

6. pflag.StringToString(name, shorthand, value, usage)

Сигнатура: func StringToString(name string, shorthand string, value map[string]string, usage string) *map[string]string

Создаёт флаг для передачи пар ключ=значение. Идеально подходит для конфигурационных параметров.

config := pflag.StringToStringP("config", "c", map[string]string{}, "конфигурация в формате ключ=значение")
pflag.Parse()
fmt.Printf("Конфиг: %v\n", *config)
// Использование: --config=key1=val1 --config=key2=val2

7. pflag.Count(name, shorthand, usage)

Сигнатура: func Count(name string, shorthand string, usage string) *int

Создаёт флаг-счётчик. Каждый раз, когда флаг встречается в командной строке, значение увеличивается на 1. Полезен для указания уровня детализации (например, -vvv).

verbose := pflag.CountP("verbose", "v", "уровень детализации")
pflag.Parse()
fmt.Printf("Уровень детализации: %d\n", *verbose)
// Использование: -v, -vv, -vvv

8. pflag.Duration(name, shorthand, value, usage)

Сигнатура: func Duration(name string, shorthand string, value time.Duration, usage string) *time.Duration

Создаёт флаг для временных интервалов. Поддерживает форматы: "300ms", "1.5h", "2h45m" и т.д.

timeout := pflag.DurationP("timeout", "t", 30*time.Second, "таймаут операции")
pflag.Parse()
fmt.Printf("Таймаут: %v\n", *timeout)
// Использование: --timeout=5m30s

9. pflag.Lookup(name string)

Сигнатура: func Lookup(name string) *Flag

Позволяет найти уже определённый флаг по его имени. Возвращает объект *Flag или nil, если флаг не найден.

pflag.String("output", "stdout", "выходной файл")
if flag := pflag.Lookup("output"); flag != nil {
    fmt.Printf("Флаг '%s' найден, значение: %s\n", flag.Name, flag.Value.String())
}

10. pflag.VisitAll(fn func(*Flag))

Сигнатура: func VisitAll(fn func(*Flag))

Выполняет функцию для каждого определённого флага. Полезен для генерации справки или валидации всех флагов.

pflag.String("name", "default", "имя")
pflag.Int("port", 8080, "порт")

pflag.VisitAll(func(f *pflag.Flag) {
    fmt.Printf("Флаг: --%s (по умолчанию: %v) - %s\n", 
        f.Name, f.DefValue, f.Usage)
})

Реальный практический пример

Создадим утилиту для мониторинга веб-сайтов, которая принимает список URL, проверяет их доступность и выводит результаты в различных форматах:

package main

import (
    "fmt"
    "net/http"
    "os"
    "sync"
    "time"
    
    "github.com/spf13/pflag"
)

type SiteStatus struct {
    URL        string
    StatusCode int
    Latency    time.Duration
    Error      error
}

func main() {
    // Определяем флаги
    urls := pflag.StringSliceP("url", "u", []string{}, "URL-адреса для проверки (обязательный)")
    timeout := pflag.DurationP("timeout", "t", 10*time.Second, "таймаут для каждого запроса")
    concurrency := pflag.IntP("concurrency", "c", 5, "максимальное количество параллельных запросов")
    verbose := pflag.CountP("verbose", "v", "уровень детализации (-v, -vv)")
    format := pflag.StringP("format", "f", "table", "формат вывода: table, json, simple")
    outputFile := pflag.StringP("output", "o", "", "файл для сохранения результатов")
    
    // Парсим флаги
    pflag.Parse()
    
    // Проверяем обязательные флаги
    if len(*urls) == 0 {
        fmt.Println("Ошибка: необходимо указать хотя бы один URL через --url")
        pflag.Usage()
        os.Exit(1)
    }
    
    // Создаём HTTP клиент с таймаутом
    client := &http.Client{
        Timeout: *timeout,
    }
    
    // Канал для результатов
    results := make(chan SiteStatus, len(*urls))
    
    // Ограничиваем параллелизм с помощью семафора
    semaphore := make(chan struct{}, *concurrency)
    var wg sync.WaitGroup
    
    startTime := time.Now()
    
    if *verbose > 0 {
        fmt.Printf("Начинаем проверку %d URL-адресов...\n", len(*urls))
        if *verbose > 1 {
            fmt.Printf("Параллельность: %d, Таймаут: %v\n", *concurrency, *timeout)
        }
    }
    
    // Запускаем проверку URL
    for _, url := range *urls {
        wg.Add(1)
        go func(url string) {
            defer wg.Done()
            semaphore <- struct{}{}
            defer func() { <-semaphore }()
            
            if *verbose > 1 {
                fmt.Printf("Проверяем: %s\n", url)
            }
            
            status := checkSite(client, url)
            results <- status
        }(url)
    }
    
    // Закрываем канал после завершения всех горутин
    go func() {
        wg.Wait()
        close(results)
    }()
    
    // Собираем результаты
    var statuses []SiteStatus
    for result := range results {
        statuses = append(statuses, result)
    }
    
    elapsed := time.Since(startTime)
    
    // Выводим результаты
    output := formatResults(statuses, *format, elapsed)
    
    if *outputFile != "" {
        // Сохраняем в файл
        err := os.WriteFile(*outputFile, []byte(output), 0644)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Ошибка записи в файл: %v\n", err)
            os.Exit(1)
        }
        if *verbose > 0 {
            fmt.Printf("Результаты сохранены в: %s\n", *outputFile)
        }
    } else {
        fmt.Print(output)
    }
}

func checkSite(client *http.Client, url string) SiteStatus {
    start := time.Now()
    
    resp, err := client.Get(url)
    latency := time.Since(start)
    
    if err != nil {
        return SiteStatus{
            URL:   url,
            Error: err,
        }
    }
    defer resp.Body.Close()
    
    return SiteStatus{
        URL:        url,
        StatusCode: resp.StatusCode,
        Latency:    latency,
    }
}

func formatResults(statuses []SiteStatus, format string, elapsed time.Duration) string {
    var result string
    
    switch format {
    case "json":
        result = formatJSON(statuses, elapsed)
    case "simple":
        result = formatSimple(statuses, elapsed)
    default:
        result = formatTable(statuses, elapsed)
    }
    
    return result
}

func formatTable(statuses []SiteStatus, elapsed time.Duration) string {
    output := fmt.Sprintf("%-40s %-10s %-15s\n", "URL", "Статус", "Задержка")
    output += "------------------------------------------------------------\n"
    
    for _, s := range statuses {
        status := fmt.Sprintf("%d", s.StatusCode)
        if s.Error != nil {
            status = "ОШИБКА"
        }
        latency := fmt.Sprintf("%v", s.Latency)
        if s.Error != nil {
            latency = "-"
        }
        output += fmt.Sprintf("%-40s %-10s %-15s\n", s.URL, status, latency)
    }
    
    output += fmt.Sprintf("\nВсего проверено: %d | Время выполнения: %v\n", 
        len(statuses), elapsed)
    
    return output
}

func formatJSON(statuses []SiteStatus, elapsed time.Duration) string {
    output := "[\n"
    for i, s := range statuses {
        status := fmt.Sprintf("%d", s.StatusCode)
        if s.Error != nil                    

Рекомендаций