обзор
Язык Go — это новый захватывающий язык, который набирает популярность по уважительной причине. В этом уроке вы научитесь писать программы для командной строки с помощью Go. Пример программы называется multi-git, и он позволяет вам выполнять команды git одновременно в нескольких репозиториях.
Краткое введение в Go
Go — это C-подобный язык с открытым исходным кодом, созданный в Google некоторыми из первоначальных хакеров C и Unix, которые были мотивированы своей неприязнью к C ++. Это видно из дизайна Go, который сделал несколько неортодоксальных выборов, таких как отказ от наследования реализации, шаблонов и исключений. Go прост, надежен и эффективен. Его наиболее отличительной особенностью является его явная поддержка параллельного программирования через так называемые goroutines и каналы.
Прежде чем приступить к анализу примера программы, следуйте официальному руководству, чтобы подготовиться к разработке на Go.
Go — невероятно мощный язык программирования, изучите все от написания простых утилит до создания масштабируемых, гибких веб-серверов в нашем полном курсе.
Программа Multi-Git
Программа 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 и экспериментировать. Это очень весело.
