Почему Go для CLI
Go — отличный выбор для CLI-инструментов:
Стандартный пакет flag
▸Простой CLI
1package main23import (4 "flag"5 "fmt"6 "os"7)89func main() {10 // Определение флагов11 name := flag.String("name", "World", "Name to greet")12 count := flag.Int("count", 1, "Number of greetings")13 verbose := flag.Bool("verbose", false, "Verbose output")1415 // Парсинг16 flag.Parse()1718 if *verbose {19 fmt.Println("Starting program...")20 }2122 for i := 0; i < *count; i++ {23 fmt.Printf("Hello, %s!\n", *name)24 }25}
▸Субкоманды
1func main() {2 // Основные команды3 if len(os.Args) < 2 {4 fmt.Println("Usage: mytool <command>")5 os.Exit(1)6 }78 switch os.Args[1] {9 case "serve":10 serveCmd()11 case "migrate":12 migrateCmd()13 case "version":14 fmt.Println("v1.0.0")15 default:16 fmt.Printf("Unknown command: %s\n", os.Args[1])17 os.Exit(1)18 }19}
Cobra: фреймворк для CLI
▸Установка
1go get github.com/spf13/cobra
▸Создание проекта
1cobra init myapp2cd myapp3cobra add serve4cobra add migrate
▸Основная структура
1// cmd/root.go2package cmd34import (5 "fmt"6 "os"7 "github.com/spf13/cobra"8)910var rootCmd = &cobra.Command{11 Use: "myapp",12 Short: "My awesome CLI tool",13 Long: "A longer description of your CLI tool",14}1516func Execute() {17 if err := rootCmd.Execute(); err != nil {18 fmt.Println(err)19 os.Exit(1)20 }21}2223func init() {24 rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "", "config file")25 rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")26}
▸Субкоманды
1// cmd/serve.go2package cmd34import (5 "fmt"6 "github.com/spf13/cobra"7)89var serveCmd = &cobra.Command{10 Use: "serve",11 Short: "Start the server",12 RunE: func(cmd *cobra.Command, args []string) error {13 port, _ := cmd.Flags().GetInt("port")14 fmt.Printf("Starting server on port %d\n", port)15 return nil16 },17}1819func init() {20 serveCmd.Flags().IntP("port", "p", 8080, "Port to listen on")21 rootCmd.AddCommand(serveCmd)22}
Viper: конфигурация
▸Основы
1package main23import (4 "fmt"5 "github.com/spf13/viper"6)78func main() {9 viper.SetConfigName("config")10 viper.SetConfigType("yaml")11 viper.AddConfigPath(".")12 viper.AddConfigPath("$HOME/.myapp")1314 // Значения по умолчанию15 viper.SetDefault("server.port", 8080)16 viper.SetDefault("server.host", "localhost")1718 // Переменные окружения19 viper.SetEnvPrefix("MYAPP")20 viper.AutomaticEnv()2122 // Чтение конфига23 if err := viper.ReadInConfig(); err != nil {24 fmt.Printf("Config error: %v\n", err)25 }2627 // Получение значений28 port := viper.GetInt("server.port")29 host := viper.GetString("server.host")30 fmt.Printf("Server: %s:%d\n", host, port)31}
▸config.yaml
1server:2 host: localhost3 port: 808045database:6 host: localhost7 port: 54328 name: mydb
Кроссплатформенная сборка
▸Makefile
1BINARY_NAME=myapp2VERSION=$(shell git describe --tags --always)34build-linux:5 GOOS=linux GOARCH=amd64 go build -o $(BINARY_NAME)-linux-amd64 .67build-darwin:8 GOOS=darwin GOARCH=arm64 go build -o $(BINARY_NAME)-darwin-arm64 .910build-windows:11 GOOS=windows GOARCH=amd64 go build -o $(BINARY_NAME)-windows-amd64.exe .1213build-all: build-linux build-darwin build-windows1415clean:16 rm -f $(BINARY_NAME)-*
▸goreleaser
1# .goreleaser.yml2builds:3 - binary: myapp4 goos:5 - linux6 - darwin7 - windows8 goarch:9 - amd6410 - arm641112archives:13 - format: tar.gz14 format_overrides:15 - goos: windows16 format: zip1718checksum:19 name_template: 'checksums.txt'2021snapshot:22 name_template: "{{ .Tag }}-next"
Логирование
▸structured logging
1package main23import (4 "log/slog"5 "os"6)78func main() {9 logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))10 slog.SetDefault(logger)1112 slog.Info("server started",13 "port", 8080,14 "host", "localhost",15 )1617 slog.Error("failed to connect",18 "error", err,19 "host", dbHost,20 )21}
Best Practices
▸Структура CLI проекта
1myapp/2├── cmd/3│ ├── root.go4│ ├── serve.go5│ └── migrate.go6├── internal/7│ ├── server/8│ └── db/9├── config/10│ └── config.yaml11├── main.go12├── go.mod13└── Makefile
▸Тестирование CLI
1func TestServeCommand(t *testing.T) {2 cmd := rootCmd3 cmd.SetArgs([]string{"serve", "--port", "9090"})45 buf := new(bytes.Buffer)6 cmd.SetOut(buf)78 err := cmd.Execute()9 if err != nil {10 t.Fatalf("unexpected error: %v", err)11 }1213 if !strings.Contains(buf.String(), "9090") {14 t.Errorf("expected port 9090 in output")15 }16}
Заключение
Go — идеальный язык для создания CLI-инструментов. Cobra и Viper упрощают создание профессиональных CLI с конфигурацией и субкомандами. На собеседовании важно понимать паттерны проектирования CLI и как создавать кроссплатформенные инструменты.