Библиотека 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 Также в библиотеке
candle для Rust: полное руководство с примерами
Очереди задач в Go: Обзор библиотек async, gocelery и machinery
CLI фреймворки для PHP: создание консольных приложений с Symfony Console и Laravel Zero
Планировщик задач на Node.js (node-cron, node-schedule, Bull)