routine tasks automation

Введение


Машины всегда будут быстрее, независимо от того насколько мы продуктивны и как быстро мы набираем команды. Суровая правда жизни. С другой стороны, если мы выполняем одно и тоже действие множество раз, то почему бы не заставить машины страдать. Написать скрипт на bash (ваш любимый язык программирования) и каждый раз вызывать этот скрипт, а не набирать монотонные команды, которые забирают так много времени, сил и энергии. А мы, пока скрипт будет выполнять свою работу, можем помечтать о том, как космические корабли бороздят просторы нашей Вселенной.

В прошлой статье мы рассмотрели основы программирования на bash. Сегодня мы будем применять полученные знания на практике.

План автоматизации


  1. Быстрый diff
  2. Быстрый diff + Jira API
  3. Очистка _dist
  4. Up большого числа репозиториев
  5. Клонирование большого числа репозиториев
  6. Полезные алиасы

В этот список попали задачи, которые я выполняю ежедневно несколько раз в день, а то и в час. Вообще, автоматизация рутинных процессов — это творческий и сугубо личный процесс.
Автоматизировать можно все, что вы только можете придумать. Надеюсь, что в конце статьи у вас появится свой план по автоматизации и вы заставите машины страдать. Сделайте себе чашечку ароматного кофе и усаживайтесь поудобней. Вас ждет увлекательное путешествие в мир автоматизации средствами bash.

Быстрый diff


В своей работе на проекте мы используем Git. Создание diff является довольно частой задачей. Чтобы создать diff для конкретной ветки нужно выполнить следующую команду:

git diff origin/master origin/<branch-name> > "${HOME}/diff/diff-<branch-name>.diff"

<branch-name> — имя ветки для которой нужно создать diff

Недостатки данного подхода


  1. Каждый раз команду нужно набирать вручную
  2. Большая вероятность ошибки в процессе набора
  3. Сложно запомнить

Эти недостатки можно с легкостью решить при помощи bash. В идеале все должно работать так:
  1. Набрали команду
  2. Передали ей имя ветки
  3. Получили diff

Финальный вид команды


gdd <branch-name>

Автоматизируем



Теперь вместо длинной строки команд, достаточно набрать на клавиатуре ./fast_diff.sh <branch-name>. Если забыли указать имя ветки, нам об этом любезно сообщит данный скрипт.

Финальный штрих


Стоп, скажете вы. А как же финальный вид команды. Ведь в текущем виде данный скрипт не очень удобен, мы привязаны к директории в которой он находится.

Рассмотрим более подробно как сделать для исполняемого файла отдельную команду, а не писать к нему каждый раз относительный/абсолютный путь.

У каждого пользователя в его домашней директории (~) располагается поддиректория bin. Если такой нет, то ее можно создать. В ней могут хранится исполняемые файлы. Удобство заключается в том, что такие файлы доступны по имени и к ним не нужно указывать относительный/абсолютный путь. В эту директорию я поместил файл gdd, который отвечает за создание diff:

#!/bin/bash

"${HOME}/htdocs/rybka/tools/fast_diff.sh" "$@"

Несколько важных моментов:

  1. У файла не принято указывать расширение.
  2. Для файла нужно явно указать атрибут x (chmod +x <filename>).
  3. Если директория bin не находится в переменной $PATH, нужно добавить ее явно PATH="${PATH}:${HOME}/bin".

Для того, чтобы этот файл стал доступным, перезапустите терминал. Теперь, чтобы создать diff, достаточно выполнить следующую команду:

gdd <branch-name>

Не очень удобно все время создавать отдельный файл в директории bin для каждой команды. Мы можем это оптимизировать воспользовавшись символическими ссылками:

ln -s "${HOME}/htdocs/rybka/tools/fast_diff.sh" gdd

Быстрый diff + Jira API


Если вы используете в своей работе менеджер задач Jira или любой другой менеджер задач, который предоставляет API, то можно пойти еще дальше. К примеру, можно с помощью Jira API прикреплять diff к конкретной задаче. Для этого нам понадобится еще и cURL.

Алгоритм решения


  1. Вызываем скрипт
  2. Передаем id задачи
  3. Если id задачи не передано, выводим сообщение пользователю
  4. Если все верно, генерируем diff и прикрепляем его к задаче

Финальный вид команды


gdd_jira <issue_id>

Автоматизируем



Как вы могли заметить, имя ветки не нужно указывать. Ее мы получаем при помощи нехитрых манипуляций с командами git:

branch=$(git rev-parse --abbrev-ref HEAD)

Очистка _dist


Для начала, давайте разберемся за что отвечает директория _dist. Это место, куда попадают файлы CSS, JavaScript, всевозможные шаблоны (Jade/Pug, Handlebars, др.) и прочие файлы после запуска системы сборки (Grunt, Gulp, др.). Эта директория не обязательно должна называться _dist. Возможны вариации.

Очистка _dist
Для одного из проектов мы используем Grunt. Довольно часто наша команда сталкивается с проблемой, что Grunt не всегда замечает изменения в некоторых файлах (проблема в основном, с Less файлами). Чтобы исправить эту ситуацию нужно очистить директорию _dist для одной из тем или для всех тем сразу. Да, данную задачу можно решить и с помощью Grunt. Даже можно все время удалять данную директорию вручную. Но это будет не так эффективно и удобно, как в случае с bash. Количество этих директорий (_dist) не одна и не две, и даже не десять или двадцать. Их много. Основное требование к скрипту — не применять лишние обертки и/или зависимости без необходимости.

Рассмотрим вариант без применения bash. Используем всю мощь оболочки для решения этой задачи:

find <path-to-themes> -type d -name "_dist" | xargs rm -rfv

<path-to-themes> — путь к директории, где находятся все темы

Недостатки у данного подхода примерно те же, что я приводил и в случае с задачей по созданию diff. Плюс ко всему в данном случае нет возможности указывать для какой конкретно темы нужно удалить директорию _dist.

Алгоритм решения


  1. Вызываем скрипт
  2. Если название темы не было передано, удаляем директорию _dist во всех темах
  3. Если имя темы было передано, выполняем удаление директории _dist в конкретной теме

Финальный вид команды


clean_dist [<theme_name>]

Автоматизируем



Up большого числа репозиториев

Up большого числа репозиториев
Представьте, что вы работаете с большим проектом. В этом проекте есть директория, отведенная под сторонние репозитории, которые вы не разрабатываете, но поддерживать их в актуальном состоянии обязаны. Конечно, если этих репозиториев два-три, то это не такая большая проблема. Хотя и тут я бы поспорил. А если у вас таких репозиториев 10-15, и эта цифра постоянно растет. В результате вы не успеваете за ними следить или тратите несоизмеримо много времени на поддержку. Почему бы эту задачу не автоматизировать.

Алгоритм решения


  1. Перейти в директорию с репозиторием
  2. Проверить, чтобы репозиторий был на ветке master
  3. Если репозиторий не на ветке master, сделать git checkout
  4. Сделать git pull

Важный момент. Даже, если репозиторий находится на ветке master, мы не можем быть уверены, что он в актуальном состоянии. Исходя из этого, делаем git pull в любом случае.

Финальный вид команды


up_repo

Автоматизируем



Клонирование большого числа репозиториев


Данная задача тесно связана с предыдущим пунктом автоматизации. Чтобы конечный пользователь имел возможность воспользоваться предыдущей командой на практике, необходимо предоставить набор репозиториев сторонних разработчиков, которые будут располагаться в директории bash/core/vendors, и о которых пользователю, по большому счету, не нужно ничего знать. По аналогии с npm модулями этот набор репозиториев не должен поставляться вместе с основным репозиторием. Все что нужно сделать пользователю — это выполнить команду и дождаться завершения клонирования репозиториев.

Алгоритм решения


  1. Список репозиториев задан в виде массива
  2. Запускаем цикл по этому массиву
  3. Уделяем особое внимание, если один вендор имеет больше одного репозитория
  4. Выполняем дополнительные проверки
  5. Выполняем git clone

Финальный вид команды


clone_repo

Автоматизируем



Полезные алиасы


У меня есть несколько вопросов к читателям. Вы должны ответить честно самому себе. Как часто вы используете эту команду?

git branch

А эту команду?

git status

А вот эту команду?

git push origin <branch-name>

Как насчет этой команды?

ps aux | grep <user-name>

Все верно, этот список можно продолжать бесконечно и он у каждого свой. И тут, неожиданно, к нам приходит озарение:

Aliases

Правильно. Для команд, которые вы используете часто — создавайте алиасы. Вот лишь небольшой список тех алиасов, что я использую:


Чтобы проверить какие алиасы у вас заданы, достаточно выполнить команду alias без параметров.

Куда поместить алиасы


В большинстве случаев для таких целей используют файл .bashrc, который располагается в домашней директории пользователя. Еще есть файл под названием .gitconfig, в который можно добавлять алиасы для работы с git.

Не стоит менять алиасы поздно ночью


Алиасы — это мощный инструмент. Только тут как с паролями. Не стоит менять алиасы поздно ночью. В одну прекрасную ночь, я поменял один из алиасов и забыл. На следующий день я потратил полдня, разбираясь почему ничего не работает.

Вместо заключения


Как только я начал разбираться с основами программирования на bash, первая мысль, которая меня посетила: «Стоп, это же нужно больше для системных администраторов...». Но в то же время, я понимал, что мне нужны эти знания, чтобы хоть как-то избавить себя от ежедневных рутинных задач. Сейчас я могу с уверенностью сказать, что эти знания нужны не только системным администраторам. Они пригодятся всем, кто хоть как-то взаимодействует с удаленным сервером или работает на OS *nix подобных системах. Для пользователей, которые работают на Windows OS эти знания тоже пригодятся (Bash on Ubuntu on Windows, Windows and Ubuntu Interoperability). В простейшем случае, скрипт — это ни что иное, как простой список команд системы, записанный в файл. Такой файл может облегчить вам рабочие будни и избавит от необходимости выполнять рутинные задачи вручную.

Полезные ссылки по некоторым возможностям bash, которые были использованы в примерах:

  1. Перенаправление ввода/вывода
  2. Функции
  3. Массивы
  4. Двойные круглые скобки
  5. Объединение команд в цепочки (конвейер)
  6. Завершение и код завершения
  7. Псевдонимы
  8. Как правильно добавлять пути в переменную $PATH

На этом все. Спасибо за внимание. Кто дочитал до конца, отдельное спасибо.
Поделиться с друзьями
-->

Комментарии (30)


  1. alexyr
    20.02.2017 10:04
    +2

    1. У файла не должно быть расширения

    Откуда такая информация? Всё прекрасно работает из bin с расширениями! Красивее — это да…

    И вообще, как вариант, в файл gdd можно положить весь код из fast_diff.sh, а не вызов самого fast_diff.sh


    1. var_bin
      20.02.2017 11:48

      Добрый день, Алексей.


      Да, вы правы, работает с расширением и можно весь код запихнуть в этот файл. Без расширения, чтобы обращение к файлу выглядело как команда, а не выводило мне всякий раз расширение. По поводу положить весь код в файл gdd. Не делаю так из соображений организации кода. Все подобные файлы располагаются в директории tools, а те файлы которые отвечают за их вызов в директории bin. Плюс ко всему, директория tools находится под git'ом.


      1. ZyXI
        20.02.2017 17:32
        +1

        Здесь логичнее было бы написать «не принято указывать». «Не должно быть» неверно.


  1. alexyr
    20.02.2017 10:12
    +3

    2. Если название темы не было передано, удаляем директорию _dist во всех темах

    А это опасно! Лучше прописать флаг, типа --all, для удаления всего. А на отсутствие аргументов вывести help. Иначе можно случайно удалить всё, не желая того… Возможно в конкретно этом случае ничего страшного, но деструктивные действия без подтверждения это «фу-фу-фу»


    1. var_bin
      20.02.2017 11:51

      Полностью согласен с вами, что это опасно ) И вы правильно заметили по поводу флага, типа --all и help. Почти так оно сейчас и работает. На хотел перегружать пример лишним кодом. Мне хотелось, чтобы читатель заметил, где можно сделать лучше и что можно еще больше оптимизировать, защитить и т.д. Вы заметили — значит все круто.


  1. tytar
    20.02.2017 12:15

    ИМХО, у алиасов есть две стороны медали — с одной стороны удобно, очень удобно. А с другой, если ты работаешь только на своей машине то, в принципе, другой стороны нет. Но, если периодически приходиться работать с удаленными, то любое gf, gm и т.д. выливаються небольшую, совсем маленькую, но очень бесящую «боль».


    1. var_bin
      20.02.2017 12:20

      Добрый день!


      Да, согласен, есть такое. Но если у вас есть права на запись в файл .bashrc и на удаленном сервере все навтроено верно (каждый пользователь со своими настройками), то не составляет труда перенести туда свои алиасы. Я так и сделал. И не испытываю неудобств


      1. tytar
        20.02.2017 12:22

        Это пока Вы не поднимаете пачками инстансы, и когда нуджно каждый по своему настраивать. ДевОпс, такой ДевОпс.


        1. thunderspb
          20.02.2017 13:32

          ну пользователя тоже можно запровиженить налету :) можно ансиблом донастроить своего пользователя


    1. olegkrasnov
      21.02.2017 03:55

      sshrc не поможет?


  1. ZyXI
    20.02.2017 12:21

    У каждого пользователя в его домашней директории (~) располагается поддиректория bin. Если такой нет, то ее можно создать. В ней могут хранится исполняемые файлы. Удобство заключается в том, что такие файлы доступны по имени и к ним не нужно указывать относительный/абсолютный путь. В эту директорию я поместил файл gdd, который отвечает за создание diff:

    Впервые слышу про ~/bin: у меня он есть, но в $path я этот каталог добавлял сам в ~/.zshrc. Как и ~/.local/bin (туда pip кладёт; может и ещё кто — каталог выглядит более стандартным, к тому же ~/.local/share является значением по?умолчанию в XDG Base Directory Specification), ~/.luarocks/bin и ~/.gem/ruby/*/bin.


    И зачем делать скрипт, вызывающий другой скрипт, есть же символические ссылки?


    1. ZyXI
      20.02.2017 12:25

      Для того, чтобы этот файл стал доступным, перезапустите терминал. Теперь, чтобы создать diff, достаточно выполнить следующую команду:

      А перезапускать?то зачем? Если ~/bin уже был в $PATH, то оно сразу работать начнёт, если нет — после того, как добавите. В zsh ещё нужно написать rehash: без него тоже работает, но автодополнения берёт данные из кэша, который не обновляется автоматически.


      1. var_bin
        20.02.2017 12:36
        -1

        Может у меня с кармой что-то не так, но без перезапуска не рабоает


        1. ZyXI
          20.02.2017 12:51

          Не имею понятия, как получить такой же эффект неспециально.


    1. var_bin
      20.02.2017 12:35
      -1

      Добрый день, Николай.


      Впервые слышу про ~/bin:

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


      И зачем делать скрипт, вызывающий другой скрипт, есть же символические ссылки?

      Хм, интересный вопрос. С такой ссылкой останется тот же функционал, что я описал в статье?


      1. ZyXI
        20.02.2017 12:48
        +1

        Хм, интересный вопрос. С такой ссылкой останется тот же функционал, что я описал в статье?

        Да, bash не проверяет, символическая ссылка или нет, он проверяет только можно ли выполнить файл через stat(), а stat() stat’ит файлы по ссылке. Zsh делает больше системных вызовов и других, но тоже не обрабатывает символические ссылки специальным образом. У меня ~/bin – символическая ссылка и больше половины файлов внутри символические ссылки и это работает.


        1. var_bin
          20.02.2017 13:09

          Да, вы правы, все работает. Только что перепроверил. Спасибо.
          Добавлю этот пункт в статью


      1. ZyXI
        20.02.2017 12:56

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

        Как ограничены? Либо из каталога можно читать файлы и их можно тогда выполнить, либо нет. Единственный вариант, что мне приходит в голову — монтировать с noexec, но кто и зачем будет это делать с ~/.local? Я бы не удивился, увидев на каком?нибудь компьютере /home с noexec — требования по безопасности бывают разные, — но тогда и ~/bin не поможет (если только он не символическая ссылка куда?то без noexec).


        1. var_bin
          20.02.2017 13:12

          Это только мое предположение. Всегда пользовался домашней директорией пользователя ~/bin. Да, ее явно нужно добавлять в $PATH.


          1. ZyXI
            20.02.2017 14:34

            Да, ее явно нужно добавлять в $PATH.

            Это хорошо бы упомянуть в статье.


            1. var_bin
              20.02.2017 15:41

              Спасибо за замечания. Поправил


  1. sabio
    20.02.2017 18:56

    Алиасы для Git, кстати, можно задавать в настройках самого Git.
    Некоторые интересные идеи есть, например, вот в этой статье — Human Git Aliases


    P.S. Ваш алиас 'gbd' весьма опасен. Захотите вы запустить отладчик, а вместо этого нечаянно удалите ветку (да ещё и с -D)


    1. var_bin
      20.02.2017 20:29

      Добрый день, Дмитрий.


      Алиасы для Git, кстати, можно задавать в настройках самого Git.

      Упоминал про это в статье "Еще есть файл под названием .gitconfig, в который можно добавлять алиасы для работы с git.". Не стал расписывать, чтобы не перегружать статью.


      P.S. Ваш алиас 'gbd' весьма опасен. Захотите вы запустить отладчик, а вместо этого нечаянно удалите ветку (да ещё и с -D)

      Пока живем с ним мирно =)


  1. Denkenmacht
    20.02.2017 20:04
    +1

    подобных системах. Для пользователей, которые работают на Windows OS эти знания тоже пригодятся

    Позволю себе высказать предположение, что пользователи, работающие на Windows, вряд ли будут заниматься предложенными вами по ссылкам извращениями развлечениями, а продолжат юзать VBScript.


    1. ZyXI
      20.02.2017 21:00

      Одна из первых вещей, которые я делаю на Windows, куда я могу ставить ПО — это установка babun (cygwin + пачка программ, включая zsh и эмулятор терминала).


  1. dayfuaim
    20.02.2017 21:59

    Про список алиасов:

    alias
    

    без параметров покажет все имеющиеся alias'ы. ;)


    1. var_bin
      21.02.2017 12:03

      Добрый день, Денис.


      Спасибо. Этот момент упустил. Обновил статью.



  1. Dreyk
    21.02.2017 16:18

    я настороженно отношусь к введению такого количества коротких алиасов. как уже сказали выше рано или поздно оно законфликтует с каким-нибудь бинарником


    гораздо лучше сохранить ваш скрипт fastdiff в файл git-fastdiff, который точно так же положить в $PATH и потом вызывать его как


    $ git fastdiff <branch-name>


    1. var_bin
      21.02.2017 17:48

      Добрый день.


      Все возможно. Этими алиасами пользуюсь уже больше трех лет и никаких конфликтов ). Ваш подход тоже рабочий и очень даже практичный. Но немного больше настроек.