1. Code
  2. Coding Fundamentals
  3. Version Control & Git

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

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

Russian (Pусский) translation by Ilya Nikov (you can also view the original English article)

Обзор

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

Краткое введение в Go

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

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

Программа Multi-Git

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

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

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

Пакеты и импорт

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

1
package main
2
3
4
5
import (
6
7
    "flag"
8
9
    "fmt"
10
11
    "log"
12
13
    "os"
14
15
    "strings"
16
17
    "os/exec"
18
19
)

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

Аргументы командной строки

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

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

1
func main() {
2
3
    command := flag.String("command", "", "The git command")
4
5
    ignoreErrors := flag.Bool(
6
7
        "ignore-errors",
8
9
        false,
10
11
        "Keep running after error if true")
12
13
    flag.Parse()

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

Переменные среды

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

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

1
    // Get managed repos from environment variables

2
3
    root := os.Getenv("MG_ROOT")
4
5
    if root[len(root) - 1] != '/' {
6
7
        root += "/"
8
9
    }
10
11
12
13
    repo_names := strings.Split(os.Getenv("MG_REPOS"), ",")

Проверка списка репозитория

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

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

1
    var repos []string
2
3
    // Verify all repos exist and are actually git repos (have .git sub-dir)

4
5
    for _, r := range repo_names {
6
7
        path := root + r
8
9
        _, err := os.Stat(path + "/.git")
10
11
        if err != nil {
12
13
            log.Fatal(err)
14
15
        }
16
17
        repos = append(repos, path)
18
19
    }

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

Выполнение команд оболочки

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

1
    // Break the git command into components (needed to execute)

2
3
    var git_components []string
4
5
    for _, component := range strings.Split(*command, " ") {
6
7
        git_components = append(git_components, component)
8
9
    }
10
11
    command_string := "git " + *command

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

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

1
    for _, r := range repos {
2
3
        // Go to the repo's directory

4
5
        os.Chdir(r);
6
7
8
9
        // Print the command

10
11
        fmt.Printf("[%s] %s\n", r, command_string)
12
13
14
15
        // Execute the command

16
17
        out, err := exec.Command("git", git_components...).CombinedOutput()
18
19
20
21
        // Print the result

22
23
        fmt.Println(string(out))
24
25
26
27
        // Bail out if there was an error and NOT ignoring errors

28
29
        if err != nil && !*ignoreErrors {
30
31
            os.Exit(1)
32
33
        }
34
35
    }
36
37
38
39
    fmt.Println("Done.")

Заключение

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