Интерфейс командной строки (CLI) — это отдельный тип приложений, которые выполняются исключительно в терминале командной строки.
Как правило, такие программы используются для управления различными рабочими инструментами, связанными с разработкой и поддержкой сетевой инфраструктуры.
Алгоритм взаимодействия прост:
CLI-приложения выглядят устаревшими из-за отсутствия какого либо графического интерфейса (GUI), однако по-прежнему считаются наиболее универсальным, быстрым и удобным способом системного администрирования.
Для создания CLI в Golang есть специальный пакет Cobra
, созданный сторонними разработчикам. Он построен на основе пакета flag
(парсер флагов командной строки) из стандартной библиотеки языка и предоставляет более высокий уровень абстракций.
cloud
Cobra
— это полноценная CLI-платформа для языка Go, в состав которой входит два базовых компонента:
Кстати, Cobra был разработан одним из членов команды Go, Стивом Франсом (spf13), изначально для проекта Hugo — специального фреймворка для создания веб-сайтов. Спустя время Cobra стала одним из самых популярных пакетов Golang.
Cobra предоставляет ряд довольно простых функций для создания современных интерфейсов командной строки. Помимо этого в Cobra есть высокоуровневый контроллер, помогающий организовать код разрабатываемого CLI-приложения.
Cobra реализует:
Кстати, такие крупные проекты, как Kubernetes, Hugo или CockroachDB имеют кодовую базу на Golang и для обработки команд используют именно пакет Cobra.
CLI-команды имеют довольно стандартную схему:
{приложение} {кодкоманда} [аргументы] [--флаги и их параметры]
Например, команды в реальных проектах могут выглядеть как-то так:
kubectl get all -n kube-system
Или так:
etcdctl put first second
Рабочие сущности можно поделить на три типа — все они так или иначе репрезентируют структуру команд в консольном терминале:
В стандарте POSIX есть соглашение о паттерне (схеме) организации аргументов и флагов, которому должны следовать CLI-приложения.
Это тот самый классический формат, с которым знакомо большинство разработчиков — многочисленные служебные программы Linux (например, «ls», «cp», «useradd») и сторонние приложения следуют именно ему.
Важно помнить, что схема команд четко формализована в стандарте и представляет собой следующий вид:
имя_приложения [-a] [-b] [-c аргумент] [-d|-e]
У каждого приложения может быть несколько версий одной и той же опции — длинная и короткая. При этом есть четкое правило, что короткая версия должна состоять только из одного символа.
Проверка Go
На всякий случай проверьте наличие компилятора Golang в вашей системе. Это можно сделать с помощью команды запроса версии:
go version
Если Golang действительно установлен, то в консоли появится версия Go и короткое название операционной системы.
Создание директории проекта
Далее мы создадим отдельный каталог под наш Cobra-проект:
mkdir CobraProject
После этого перейдем в него:
cd CobraProject
Golang имеет свои особенности в работе его модульной системы, необходимой для подключения пакетов. Поэтому предварительно директорию с проектом необходимо проинициализировать с помощью специальной команды:
go mod init CobraProject
После этого каталог превратится в полноценный модуль Go — в консоли появится соответствующее сообщение о создании модуля с именем CobraProject
.
Загрузка пакета из официального репозитория
Начиная с версии 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
.
Каждая введенная в консоль команда вызывает соответствующую функцию 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 // возвращаем отформатированный результат без данных об ошибке
}
Как можно видеть, функция возвращает два значения — результат и данные о возможной ошибке.
Теперь, когда функциональная часть нашего приложения готова, мы можем «зарегистрировать» команду в 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
Кстати, полный список существующих часовых поясов и их кодовые названия можно посмотреть на соответствующей странице в Википедии.
Как правило, при выполнении консольных помимо параметров можно также указывать флаги.
Флаги — это опции, которые вносят некоторые изменения в поведение конкретных команд. Флаг легко определить по предшествующему ему дефису (или двух).
Наличие флагов в 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 можно найти инструкции по установке и использованию парсера командной строки.