Интерфейс командной строки (CLI) — это отдельный тип приложений, которые выполняются исключительно в терминале командной строки.
Как правило, такие программы используются для управления различными рабочими инструментами, связанными с разработкой и поддержкой сетевой инфраструктуры.
Алгоритм взаимодействия прост:
- Пользователь вводит в терминал название CLI-приложения, имя команды, параметры и иногда дополнительные флаги
- CLI-приложение выполняет запрошенное пользователем действие и выдает текстовый ответ обратно в терминал
CLI-приложения выглядят устаревшими из-за отсутствия какого либо графического интерфейса (GUI), однако по-прежнему считаются наиболее универсальным, быстрым и удобным способом системного администрирования.
Для создания CLI в Golang есть специальный пакет Cobra, созданный сторонними разработчикам. Он построен на основе пакета flag (парсер флагов командной строки) из стандартной библиотеки языка и предоставляет более высокий уровень абстракций.
cloud
Подробнее о пакете Cobra
Cobra — это полноценная CLI-платформа для языка Go, в состав которой входит два базовых компонента:
- Библиотека для создания современных CLI-приложений
- CLI-инструмент для быстрого создания приложений на основе стандартных (для Cobra) файлов-обработчиков команд
Кстати, Cobra был разработан одним из членов команды Go, Стивом Франсом (spf13), изначально для проекта Hugo — специального фреймворка для создания веб-сайтов. Спустя время Cobra стала одним из самых популярных пакетов Golang.
Возможности Cobra
Cobra предоставляет ряд довольно простых функций для создания современных интерфейсов командной строки. Помимо этого в Cobra есть высокоуровневый контроллер, помогающий организовать код разрабатываемого CLI-приложения.
Cobra реализует:
- Иерархию обработки команд
- Мощный анализ аргументов и флагов
- Иерархию флагов (глобальные и локальные)
- Проверку подкоманд
- Совместимость со стандартом POSIX
- Автоматическое создание справки для команд и флагов
Кстати, такие крупные проекты, как Kubernetes, Hugo или CockroachDB имеют кодовую базу на Golang и для обработки команд используют именно пакет Cobra.
CLI-команды имеют довольно стандартную схему:
{приложение} {кодкоманда} [аргументы] [--флаги и их параметры]
Например, команды в реальных проектах могут выглядеть как-то так:
kubectl get all -n kube-system
Или так:
etcdctl put first second
Архитектура Cobra
Рабочие сущности можно поделить на три типа — все они так или иначе репрезентируют структуру команд в консольном терминале:
- Команды (Commands). Указывают на конкретные действия, которые необходимо выполнить. Впрочем, как в и любом классическом CLI-приложении.
- Аргументы (Args). Это некоторые вещи или сущности, которые передаются в команду, после чего она работает с ними и возвращает результат.
- Флаги (Flags). Короткие модификаторы команд (то есть конкретных действий), которые вносят определенные корректировки в выполнение работ и влияют на конечный результат работы CLI-приложения.
Немного о POSIX-совместимости
В стандарте POSIX есть соглашение о паттерне (схеме) организации аргументов и флагов, которому должны следовать CLI-приложения.
Это тот самый классический формат, с которым знакомо большинство разработчиков — многочисленные служебные программы Linux (например, «ls», «cp», «useradd») и сторонние приложения следуют именно ему.
Важно помнить, что схема команд четко формализована в стандарте и представляет собой следующий вид:
имя_приложения [-a] [-b] [-c аргумент] [-d|-e]
У каждого приложения может быть несколько версий одной и той же опции — длинная и короткая. При этом есть четкое правило, что короткая версия должна состоять только из одного символа.
1. Настройка окружения
Проверка Go
На всякий случай проверьте наличие компилятора Golang в вашей системе. Это можно сделать с помощью команды запроса версии:
go version
Если Golang действительно установлен, то в консоли появится версия Go и короткое название операционной системы.
Создание директории проекта
Далее мы создадим отдельный каталог под наш Cobra-проект:
mkdir CobraProject
После этого перейдем в него:
cd CobraProject
Golang имеет свои особенности в работе его модульной системы, необходимой для подключения пакетов. Поэтому предварительно директорию с проектом необходимо проинициализировать с помощью специальной команды:
go mod init CobraProject
После этого каталог превратится в полноценный модуль Go — в консоли появится соответствующее сообщение о создании модуля с именем CobraProject.
2. Подключение пакета Cobra
Загрузка пакета из официального репозитория
Начиная с версии Go 1.18 в Golang существует специальная команда go install, автоматически выполняющая установку удаленных модулей.
Поэтому мы воспользуемся именно ей, загрузив пакет Cobra из официального репозитория на github:
go install github.com/spf13/cobra-cli@latest
Обратите внимание, что в конце есть указатель latest — мы устанавливаем самый последний релиз.
Инициализация CLI
В терминале нам станет доступен исполняемый файл cobra-cli. С помощью него мы инициализируем проект Cobra в нашем рабочей каталоге — к этому моменту вы должны находиться уже в нем:
cobra-cli init
После этого в рабочем каталоге появятся файлы, содержащие некий стандартный код пакета Cobra с названием проекта — CobraProject.
Структура файлов будет иметь следующий вид:
CobraProject/
cmd/
root.go
main.go
go.mod
go.sum
Файл main.go является входной точкой (entry point) в CLI-приложение. Его стандартное содержимое примерно такое:
package main
import (
"CobraProject/cmd" // путь может отличаться в зависимости от расположения рабочей директории
)
func main() {
cmd.Execute()
}
Все команды размещаются в виде отдельных файлов в каталоге /cmd. При этом файл root.go является корневым обработчиком команд — по сути это базовая команда любого консольного интерфейса.
Например, рассмотрим следующую команду:
go get URL
Здесь go является корневой командой, которая обрабатывается root.go, get — дочерняя команда, обработчик которой размещен в отличном от root.go файле.
Сборка CLI
Для сборки CLI-приложения используется та же самая команда, что и для создания обычного двоичного файла проекта Go:
go build
Стандартно исполняемый файл появится в рабочем каталоге проекта.
Чтобы использовать собранное CLI-приложение, его также необходимо установить:
go install
После этого CLI-приложение станет доступно для вызова из терминала командной строки. Чтобы воспользоваться им достаточно написать в консоль название проекта:
CobraProject
Если все работает корректно, то в консоле появится стандартный вывод для команды без параметров. Разумеется, в дальнейшем стандартный вывод можно будет изменять — это делается в файле root.go.
3. Создание функции для команды
Каждая введенная в консоль команда вызывает соответствующую функцию Go, которая выполняет логику этой команды. При этом любые параметры и флаги, указанные в консоли, передаются в функцию.
В качестве простого примера мы реализуем небольшую функцию, которая отсчитывает время в текущем часовом поясе. Для этого мы задействуем пакет time.
После инициализации CLI в рабочем каталоге должна была появиться директория cmd. Перейдем в нее:
cd cmd
Теперь создадим файл, который будет содержать нашу функцию:
touch timefunc.go
Код внутри такой:
package cmd // указываем название нашего пакета
import "time" // импортируем стандартный пакет времени Go
func getTimeFromZone(zone string) (string, error) {
loc, err := time.LoadLocation(zone) // узнаем текущую локацию
// проверяем на ошибку
if err != nil {
return "", err // возвращаем пустой результат с данными об ошибке
}
timeNow := time.Now().In(loc) // получаем текущее время на основе локации
return timeNow.Format(time.RFC1123), nil // возвращаем отформатированный результат без данных об ошибке
}
Как можно видеть, функция возвращает два значения — результат и данные о возможной ошибке.
4. Добавление команды в CLI
Теперь, когда функциональная часть нашего приложения готова, мы можем «зарегистрировать» команду в CLI-приложении для доступа извне.
Для этого существует отдельная команда add:
cobra-cli add timefromzone
После этого в папке cmd появится файл timefromzone.go со стандартным кодом внутри.
Кстати, в этой же папке у вас уже расположен файл root.go отвечающий за «корневую» обработку команды — то есть команды без каких-либо параметров.
Несложно догадаться, что «обработчики» консольных команд формируются в файловой системе операционной системы в виде отдельных go-исходников.
Давайте откроем новый файл и наполним его следующим кодом:
package cmd
import (
"fmt"
"log"
"github.com/spf13/cobra"
)
var timefromzoneCmd = &cobra.Command {
Use: "timefromzone",
Short: "Возвращает время из заданной географической зоны",
Long: `Команда возвращает время из заданной географической зоны. Принимает только один аргумент — зона, время которой необходимо узнать. Результат возвращается в формате стандарта RFC1123.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
timefromzone:= args[0]
timeNow, err := getTimeFromZone(timefromzone)
if err != nil {
log.Fatalln("Неверная временная зона")
}
fmt.Println(timeNow)
},
}
func init() {
rootCmd.AddCommand(timefromzoneCmd) // добавляем новую команду к корневой команде
}
Разберемся, что означает каждое поле:
Use. Название, под которым будет доступна команда из терминалаShort. Краткое описание команды, которое будет доступно пользователю из консолиLong. Полное описание команды, которое будет доступно пользователю из консолиArgs. Точное количество аргументов, необходимое для работы командыRun. Функция-обработчик, внутри которой мы вызываем и обрабатываем ранее созданную функциюgetTimeFromZone.
На самом деле в некоторых случаях вы могли бы упростить кодовую базу, написав нужную логику прямо внутри функции-обработчика команды:
import "time"
var timefromzoneCmd = &cobra.Command {
Use: "timefromzone",
Short: "Возвращает время из заданной географической зоны",
Long: `Команда возвращает время из заданной географической зоны. Принимает только один аргумент — зона, время которой необходимо узнать. Результат возвращается в формате стандарта RFC1123.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
zone := args[0]
loc, err := time.LoadLocation(zone)
if err != nil {
log.Fatalln("Временная зона указана неверно")
}
fmt.Println(time.Now().In(loc).Format(time.RFC1123))
},
}
Все! Команда добавлена. Остается только заново переустановить наше CLI-приложение:
go install
Теперь мы можем обратиться к нашему приложению через консоль, указав название команды и передав в качестве аргумента кодовое имя часового пояса:
CobraProject timefromzone Europe/Moscow
Консольный вывод будет примерно таким:
Fri, 10 Nov 2023 22:41:06 Europe/Moscow
Кстати, полный список существующих часовых поясов и их кодовые названия можно посмотреть на соответствующей странице в Википедии.
5. Добавление флагов в CLI
Как правило, при выполнении консольных помимо параметров можно также указывать флаги.
Флаги — это опции, которые вносят некоторые изменения в поведение конкретных команд. Флаг легко определить по предшествующему ему дефису (или двух).
Наличие флагов в CLI-приложении добавляет вариативность и гибкость в поведение команд. Без флагов пришлось бы создавать множество сложных функций с большим количеством повторяющегося кода.
В этом смысле флаги позволяют унифицировать консольное приложение. При этом в Cobra флаги можно поделить на два условных типа:
- Локальные. Действуют только в рамках конкретной команды.
- Постоянные. Могут применяться сразу ко всем командам и их подкомандам.
Давайте перейдем в ранее созданный файл timefromzone.go и изменим в конце функцию инициализации, добавив обработку флагов:
func init() {
rootCmd.AddCommand(timefromzoneCmd) // добавили выше определенную команду к корневой команде
timefromzoneCmd.Flags().String("format", "", "Выводит время в формате yyyy-mm-dd") // добавили флаг к выше определенной команде, указав информацию о нем
}
Теперь мы можем воспользоваться флагом в функции Run определенной команды. В этом случае полный код файла timefromzone.go окажется таким:
package cmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
)
var timefromzoneCmd = &cobra.Command {
Use: "timefromzone",
Short: "Возвращает время из заданной географической зоны",
Long: `Команда возвращает время из заданной географической зоны. Принимает только один аргумент — зона, время которой необходимо узнать. Результат возвращается в формате стандарта RFC1123.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var date string
zone := args[0]
loc, _ := time.LoadLocation(zone)
fla, _ := cmd.Flags().GetString("format")
if fla != "" {
date = time.Now().In(loc).Format(fla)
} else {
date = time.Now().In(loc).Format(time.RFC1123)
}
fmt.Printf("Текущее время в часовом поясе %v: %v\n", loc, date)
},
}
func init() {
rootCmd.AddCommand(timefromzoneCmd)
timefromzoneCmd.Flags().String("format", "", "Выводит время в формате yyyy-mm-dd")
}
Повторно переустановим наше CLI-приложение:
go install
И выполним созданную команду, но уже с флагом:
CobraProject timefromzone Europe/Moscow --format 2006-01-02
Теперь за счет флага команда «видит» явно указанный формат даты (ГГГГ-ММ-ДД) и выводит следующий результат:
Текущее время в часовом поясе Europe/Moscow: 2023.11.10
Подготовили для вас выгодные тарифы на облачные серверы
Заключение
Пакет Cobra для языка программирования Golang — отличное решение, помогающее абстрагироваться от сложных низкоуровневых функций парсинга командной строки, которые предоставляет стандартная библиотека.
Можно сказать, Cobra — своего рода фреймворк для CLI-приложений, который снимает с разработчика «головную боль» при работе с консольным терминалом и фокусирует ресурсы преимущественно на бизнес-логике.
Каждая команда представляет собой отдельный файл в директории /cmd, который можно параметризировать с помощью флагов.
Это удобно, ведь можно в явном виде выстраивать иерархию команд и контролировать процесс их обработки за счет редактирования hook-функций, вроде init или run.
Благодаря такой особенности Cobra CLI-приложение приобретает более структурный вид и имеет меньше беспорядка, формируя определенный фреймворк.
Помните, что Cobra создана сторонними разработчиками, поэтому размещена в отдельном репозитории на GitHub и не является частью стандартной библиотеки Golang.
Также на официальном сайте Cobra можно найти инструкции по установке и использованию парсера командной строки.
