Bash

Мало что радует так же сильно, как одна красивая Bash-строка, способная автоматизировать часы рутинной работы.

Я изучала вопрос по автоматическому восстановлению системы на ноутбуке с помощью Bash-скриптов и искала простой способ клонирования GitHub-репозиториев на новую машину. Немного покопавшись в коде, мне удалось написать однострочник, который смог все это реализовать.

Затем, чтобы, так сказать, отделить мух от котлет, я написала еще один однострочник для автоматического создания и отправки резервных копий на GitLab. Привожу эти полезные команды.

Клонирование всех GitHub-репозиториев

Пояснение: вам нужен список GitHub-репозиториев для клонирования. Самое классное здесь то, что вы можете выделять только нужные вам репозитории, а не копировать все без разбора.

Чтобы клонировать GitHub-репозитории и не вводить каждый раз пароли, вам пригодится HTTPS с 15-минутным кэшированием учетных данных или мой любимый метод — подключение к GitHub через SSH. Для краткости мы воспользуемся вторым способом с уже настроенными SSH-ключами.

Например, вот в файле gh-repos.txt у нас есть такой список GitHub-адресов:

[email protected]:username/first-repository.git
[email protected]:username/second-repository.git
[email protected]:username/third-repository.git

Выполняем следующую команду:

xargs -n1 git clone < gh-repos.txt

Она клонирует все репозитории из списка в текущую папку. Если правильно указать URL, то этот же одностраничник будет работать и для GitLab.

Как это работает?

Данный однострочник состоит из двух частей. Справа, как ни странно, расположены входные значения, а слева — та часть, которая в принципе и делает всю работу. Чтобы сделать эту команду еще более понятной, можно записать ее в следующем виде:

<gh-repos.txt xargs -n1 git clone

Чтобы выполнить команду для каждой строки входных значений из gh-repos.txt, мы воспользуемся xargs -n1. Инструмент xargs считывает элементы из входных значений и выполняет все найденные команды (либо возвращает echo, если ничего не найдено). По умолчанию элементы разделяются пробелами. Можно добавить новые строки, благодаря чему список легче читается. Флажок -n1 предписывает xargs воспользоваться 1 аргументом, либо, как в нашем случае, одной строкой на команду. Эта команда создается через git clone, который xargs затем выполнит для каждой строки. Вот и все.

Создание и отправка нескольких репозиториев на GitLab

В отличие от GitHub, для создания нового репозитория на GitLab нам не нужно будет открывать сайт. Создавать новый GitLab-репозиторий можно напрямую из терминала. Этому новому репозиторию автоматически присваивается статус Private, так что если вы хотите выложить репозиторий в открытый доступ, то придется делать это вручную немного позже.

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

Для корректной работы Bash-однострочника нужен список с адресами GitLab-репозиториев (которые пока что не существуют). Тут есть простое решение: можно скопировать список GitHub-репозиториев, открыть его в Vim и выполнить поиск с заменой:

cp gh-repos.txt gl-repos.txt
vim gl-repos.txt
:%s/\<github\>/gitlab/g
:wq

Так мы получили следующий файл gl-repos.txt:

[email protected]:username/first-repository.git
[email protected]:username/second-repository.git
[email protected]:username/third-repository.git

Можно создать эти репозитории на GitLab, добавить URL-адреса в качестве удаленных репозиториев, а затем отправить код в новые репозитории. Это делается по следующей команде:

awk -F’\/|(\.git)’ ‘{system(“cd ~/FULL/PATH/” $2 “ && git remote set-url origin — add “ $0 “ && git push”)}’ gl-repos.txt

Немного терпения — я все объясню. Пока что вам нужно запомнить следующее: ~/FULL/PATH/ — это полный путь к директории с вашими GitHub-репозиториями.

Обратите внимание на пару моментов:

1. Имя директории на локальной машине, где хранится репозиторий, и имя репозитория в URL должны совпадать (особенно актуально, если клонирование выполнялось через однострочник выше).

2. Каждый репозиторий извлекается в ветку для отправки (например, master).

Наш однострочник можно дополнить с учетом вышеперечисленных нюансов. Однако по скромному мнению автора лучше делать это через Bash-скрипты.

Как это работает?

Bash-однострочник берет каждую строку (или URL) из файла gl-repos.txt в качестве входного значения. С помощью awk он отсекает имя директории, в которой репозиторий хранится на локальном компьютере, и использует эти фрагменты информации для создания более длинной команды. Если мы захотим вывести результат awk через print, то увидим:

cd ~/FULL/PATH/first-repository && git remote set-url origin — add [email protected]:username/first-repository.git && git push
cd ~/FULL/PATH/second-repository && git remote set-url origin — add [email protected]:username/second-repository.git && git push
cd ~/FULL/PATH/third-repository && git remote set-url origin — add [email protected]:username/third-repository.git && git push

Давайте рассмотрим, из чего складывается эта команда.

Разделение строк через awk

Инструмент awk разделяет входные значения с помощью разделителей полей. Стандартный разделитель — это символ пробела. Однако это значение можно изменить через передачу флажка -F. Также вы можете разделять поля с помощью регулярных выражений. А поскольку URL репозиториев заданы в определенном формате, то можно получить их имена по запросу подстроки между символом косой черты / и концом URL — .git.

Здесь нам поможет регулярное выражение \/|(\.git):

  • \/ — это экранированный символ /;
  • | означает «или». Он предписывает awk искать соответствия какому-либо выражению;
  • (\.git) — это группа записи в конце нашего URL-адреса, которая сопоставляет .git с экранированным символом. Не обошлось здесь и без, своего рода, обмана: сам .git ничего нигде не отделяет (поскольку там ничего нет), но так нам проще всего заполучить нужный фрагмент.

Сначала мы указываем awk на место разделения, а затем через оператора поля получаем нужную подстроку. Для обращения к полю воспользуемся символом $ и укажем номер столбца этого поля. В нашем примере нам нужно второе поле — $2. Вот так выглядят все подстроки:

1: [email protected]:username
2: first-repository

Для использования всей строки (или же, как в нашем случае, — всего URL-адреса) можно прибегнуть к оператору поля $0. В этом случае мы просто подставляем операторы поля для имени и URL репозитория. Чтобы проверить правильность расстановки пробелов, достаточно выполнить данную команду с print.

awk -F’\/|(\.git)’ ‘{print “cd ~/FULL/PATH/” $2 “ && git remote set-url origin — add “ $0 “ && git push”}’ gl-repos.txt

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

Наша команда создается внутри круглых скобок system(). Если задать значение в скобках в качестве вывода awk, то можно будет выполнять каждую команду сразу после ее создания и вывода. Функция system() создает дочерний процесс, который выполняет нашу команду, а затем возвращается после завершения команды. Другими словами, так мы можем выполнять Git-команды в каждом репозитории последовательно и без отрыва от основного процесса, в котором awk работает с файлом вывода. Вот как выглядит итоговая сборка команды:

awk -F’\/|(\.git)’ ‘{system(“cd ~/FULL/PATH/” $2 “ && git remote set-url origin — add “ $0 “ && git push”)}’ gl-repos.txt

Использование резервных копий

Добавляя URL-адреса GitLab в качестве удаленных репозиториев, мы упрощаем процесс отправки изменений на внешние репозитории. Если в одной из своих директорий репозитория мы выполним git remote -v, то увидим:

origin [email protected]:username/first-repository.git (fetch)
origin [email protected]:username/first-repository.git (push)
origin [email protected]:username/first-repository.git (push)

Теперь простое выполнение git push без аргументов отправит текущую ветвь на оба удаленных репозитория.

Здесь стоит отметить тот факт, что git pull попытается загрузить изменения только из того удаленного репозитория, с которого изначально осуществлялось клонирование (это URL с меткой (fetch) из примера выше). Одновременная загрузка сразу из нескольких Git-репозиториев также возможна, однако реализовать ее довольно сложно.

Пара слов о краткости Bash-однострочников

При должном понимании Bash-однострочники превращаются в удобный и интересный инструмент для работы. Как минимум, такие инструменты, как xargs и awk, позволят вам автоматизировать и облегчить рутинную работу. Но есть здесь и существенные недостатки.

Bash-однострочники — не самый удачный выбор, если вы ищите простые для понимания, поддержки и реализации инструменты. Написание однострочников куда сложнее, чем создание Bash-скриптов с циклами if или while, а их читабельность еще хуже. Скорее всего, при написании однострочника где-то потеряется кавычка или закрывающая скобка, и найти это будет весьма непросто. Так зачем же нужны Bash-однострочники?

Представьте себе, что вы читаете пошаговый рецепт приготовления пирога. Вы разобрались в способе приготовления и ингредиентах, а также заготовили все необходимое. Затем вы перечитываете рецепт и понимаете, что пирог приготовится «на ура», если просто закинуть все эти ингредиенты в духовку в правильном порядке. Вы пробуете приготовить по рецепту, и все получается! Приятно, да?

Читайте также:


Перевод статьи Victoria Drake: How to write Bash one-liners for cloning and managing GitHub and GitLab repositories