Статьи

Let’s Go: программы командной строки с Golang

Язык Go — это новый захватывающий язык, который набирает популярность по уважительной причине. В этом уроке вы научитесь писать программы для командной строки с помощью Go. Пример программы называется multi-git, и он позволяет вам выполнять команды git одновременно в нескольких репозиториях.

Go — это C-подобный язык с открытым исходным кодом, созданный в Google некоторыми из первоначальных хакеров C и Unix, которые были мотивированы своей неприязнью к C ++. Это видно из дизайна Go, который сделал несколько неортодоксальных выборов, таких как отказ от наследования реализации, шаблонов и исключений. Go прост, надежен и эффективен. Его наиболее отличительной особенностью является его явная поддержка параллельного программирования через так называемые goroutines и каналы.

Прежде чем приступить к анализу примера программы, следуйте официальному руководству, чтобы подготовиться к разработке на Go.

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

  • Go Lang
    Основные принципы построения веб-серверов
    Дерек Дженсен

Программа multi-git — это простая, но полезная программа Go. Если вы работаете в команде, в которой кодовая база разбита на несколько репозиториев git, вам часто приходится вносить изменения в несколько репозиториев. Это проблема, потому что у git нет концепции нескольких репозиториев. Все вращается вокруг одного хранилища.

Это становится особенно проблематичным, если вы используете ветки. Если вы работаете с функцией, которая касается трех репозиториев, вам нужно будет создать ветку объектов в каждом из этих репозиториев, а затем не забывать проверять, извлекать, толкать и объединять их все одновременно. Это не тривиально. Multi-git управляет набором репозиториев и позволяет вам работать со всем набором одновременно. Обратите внимание, что текущая версия multi-git требует, чтобы вы создавали ветви по отдельности, но я могу добавить эту функцию позже.

Изучив способ реализации multi-git, вы многое узнаете о написании программ для командной строки на Go.

Программы Go организованы в пакеты. Программа multi-git состоит из одного файла с именем main.go. В верхней части файла указано имя пакета ‘main’, а затем список импорта. Импортируются другие пакеты, используемые multi-git.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package main
 
 
 
import (
 
    «flag»
 
    «fmt»
 
    «log»
 
    «os»
 
    «strings»
 
    «os/exec»
 
)

Например, пакет fmt используется для форматированного ввода-вывода, подобного printf и scanf в C. Go поддерживает установку пакетов из различных источников с помощью команды go get . Когда вы устанавливаете пакеты, они попадают в пространство имен в переменной среды $ GOPATH . Вы можете устанавливать пакеты из различных источников, таких как GitHub, Bitbucket, код Google, Launchpad и даже сервисы IBM DevOps, используя несколько распространенных форматов контроля версий, таких как git, subversion, mercurial и bazaar.

Аргументы командной строки являются одной из наиболее распространенных форм предоставления ввода программам. Они просты в использовании, позволяют запускать и настраивать программу в одну строку и имеют отличную поддержку синтаксического анализа на многих языках. Go называет их «флагами» командной строки и имеет пакет флагов для указания и анализа аргументов командной строки (или флагов).

Как правило, вы анализируете аргументы командной строки в начале вашей программы, и multi-git следует этому соглашению. Точкой входа является функция main() . Первые две строки определяют два флага, называемые «команда» и «ignoreErrors». Каждый флаг имеет имя, тип данных, значение по умолчанию и строку справки. flag.Parse() проанализирует фактическую командную строку, переданную программе, и заполнит определенные флаги.

01
02
03
04
05
06
07
08
09
10
11
12
13
func main() {
 
   command := flag.String(«command», «», «The git command»)
 
   ignoreErrors := flag.Bool(
 
       «ignore-errors»,
 
       false,
 
       «Keep running after error if true»)
 
   flag.Parse()

Также возможно получить доступ к неопределенным аргументам через flag.Args() . Таким образом, флаги обозначают предопределенные аргументы, а «аргументы» — необработанные аргументы. Необработанные аргументы индексируются на основе 0.

Другая распространенная форма конфигурации программы — переменные среды. Когда вы используете переменные среды, вы можете запускать одну и ту же программу несколько раз в одной и той же среде, и во всех запусках будут использоваться одни и те же переменные среды.

Multi-git использует две переменные окружения: «MG_ROOT» и «MG_REPOS». Multi-git предназначен для управления группой репозиториев git, имеющих общий родительский каталог. Это «MG_ROOT». Имена хранилища указываются в MG_REPOS в виде строки, разделенной запятыми. Чтобы прочитать значение переменной окружения, вы можете использовать os.Getenv() .

01
02
03
04
05
06
07
08
09
10
11
12
13
// Get managed repos from environment variables
 
   root := os.Getenv(«MG_ROOT»)
 
   if root[len(root) — 1] != ‘/’ {
 
       root += «/»
 
   }
 
 
 
   repo_names := strings.Split(os.Getenv(«MG_REPOS»), «,»)

Теперь, когда он нашел корневой каталог и имена всех репозиториев, multi-git проверяет, что каждый репозиторий существует под root и что это действительно git-репозиторий. Проверка так же проста, как поиск подкаталога .git для каждого каталога репозитория.

Сначала определяется массив строк с именем «repos». Затем он выполняет итерацию по всем именам репо и создает путь к репозиторию, объединяя корневой каталог и имя репо. Если [os.Stat()]() завершится неудачно для подкаталога .git, он регистрирует ошибку и завершает работу. В противном случае путь к хранилищу добавляется к массиву репозитория.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
var repos []string
 
   // Verify all repos exist and are actually git repos (have .git sub-dir)
 
   for _, r := range repo_names {
 
       path := root + r
 
       _, err := os.Stat(path + «/.git»)
 
       if err != nil {
 
           log.Fatal(err)
 
       }
 
       repos = append(repos, path)
 
   }

Go имеет уникальную возможность обработки ошибок, где функции часто возвращают как возвращаемое значение, так и объект ошибки. Посмотрите, как os.Stat() возвращает два значения. В этом случае заполнитель «_» используется для хранения фактического результата, потому что вы заботитесь только об ошибке. Go очень строг и требует использования именованных переменных. Если вы не планируете использовать значение, вам следует присвоить его «_», чтобы избежать ошибки компиляции.

На данный момент у вас есть список путей к хранилищам, где мы хотим выполнить команду git. Как вы помните, мы получили командную строку git в виде одного аргумента командной строки (флага), называемого «команда». Это нужно разделить на массив компонентов (команда git, подкоманда и опции). Вся команда в виде строки также хранится для отображения.

01
02
03
04
05
06
07
08
09
10
11
// Break the git command into components (needed to execute)
 
   var git_components []string
 
   for _, component := range strings.Split(*command, » «) {
 
       git_components = append(git_components, component)
 
   }
 
   command_string := «git » + *command

Теперь все готово для перебора каждого репозитория и выполнения команды git в каждом из них. Конструкция цикла «for … range» используется снова. Во-первых, multi-git меняет свой рабочий каталог на текущий целевой репозиторий «r» и печатает команду git. Затем он выполняет команду с помощью функции exec.Command() и печатает комбинированный вывод (как стандартный вывод, так и стандартную ошибку).

Наконец, он проверяет, была ли ошибка во время выполнения. Если произошла ошибка и флаг ignoreErrors имеет значение false, то ignoreErrors multi-git. Причиной необязательного игнорирования ошибок является то, что иногда все в порядке, если команды не выполняются в некоторых репозиториях. Например, если вы хотите извлечь ветку с именем «отличная функция» во всех репозиториях, в которых есть эта ветка, вам не важно, если проверка не удастся в репозиториях, в которых нет этой ветки.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
for _, r := range repos {
 
       // Go to the repo’s directory
 
       os.Chdir(r);
 
 
 
       // Print the command
 
       fmt.Printf(«[%s] %s\n», r, command_string)
 
 
 
       // Execute the command
 
       out, err := exec.Command(«git», git_components…).CombinedOutput()
 
 
 
       // Print the result
 
       fmt.Println(string(out))
 
 
 
       // Bail out if there was an error and NOT ignoring errors
 
       if err != nil && !*ignoreErrors {
 
           os.Exit(1)
 
       }
 
   }
 
 
 
   fmt.Println(«Done.»)

Go — простой, но мощный язык. Он предназначен для крупномасштабного системного программирования, но отлично работает и для небольших программ командной строки. Минимальный дизайн Go резко контрастирует с другими современными языками, такими как Scale и Rust, которые также очень мощные и хорошо продуманные, но имеют очень крутой курс обучения. Я призываю вас попробовать Go и экспериментировать. Это очень весело.