Автоматизация повторяющихся задач с помощью make в Ubuntu

Если у вас есть опыт установки программного обеспечения из исходного кода на сервер Linux, вы, вероятно, сталкивались с утилитой make. Этот инструмент в основном используется для автоматизации компиляции и сборки программ. Это позволяет автору приложения быстро описать этапы, необходимые для сборки этого конкретного проекта.

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

Примечание: Мануал был протестирован на Ubuntu 12.04 VPS, но практически на любом Linux-сервере он будет работать аналогичным образом.

Установка make

Утилиту можно установить по имени, но обычно она устанавливается вместе с другими инструментами, которые помогут вам скомпилировать программное обеспечение. Установите весь этот набор – он невероятно полезен в целом. Существует пакет под названием build-essential, содержащий make и другие программы:

sudo apt-get update
sudo apt-get install build-essential

Теперь у вас есть все инструменты для сборки программ, в том числе make.

Основы Makefile

Основной способ предоставления инструкций команде make – это Makefile.

На странице справки можно увидеть, что make ищет файл GNUmakefile, затем makefile, а затем Makefile. Справка рекомендует использовать файл Makefile, поскольку файл GNUmakefile предназначен для команд, специфичных для GNU.

Файл Makefile – это каталог, который определяет, что make будет искать в каталоге, в котором он был вызван.

Внутри Makefile нужно следовать определенному формату. Make использует понятия целей, источников и команд. Все эти компоненты располагаются таким образом:

target: source
command

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

Цели

Цель – указанное пользователем имя для ссылки на группу команд. В языке программирования это просто функция.

Цели выравниваются по левому краю. Ее имя должно быть единым словом (без пробелов) и заканчиваться двоеточием (:).

При вызове make цель можно указать так:

make target_name

Утилита make проверит Makefile и выполнит связанную с целью команду.

Источники

Источники – это ссылки на файлы или другие цели. Они представляют предварительные требования или зависимости для цели, с которой они связаны.

Например, в вашем файле может быть раздел, который выглядит так:

target1: target2
target1_command
target2:
target2_command

В таком случае target1 вызывается так:

make target1

Затем make перейдет в Makefile и выполнить поиск цели «target1». После утилита проверит, имеются ли какие-либо источники для этой цели.

Она найдет зависимость источника «target2» и временно перейдет к этой цели.

Здесь она проверит, есть ли у target2 какие-либо источники. Это не так, поэтому она перейдет к выполнению команды target2. На этом этапе make достигнет конца списка команд «target2» и передаст управление цели «target1». Затем она выполнил команду «target1command» и завершит работу.

Источниками могут быть либо файлы, либо цели. Make использует временные метки, чтобы увидеть, был ли файл изменен с момента его последнего вызова. Если в файле появились изменения, эта цель запускается повторно. В противном случае make отмечает зависимость как выполненную и переходит к следующему источнику или к команде.

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

Команды

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

Команды указываются в строке после объявления цели. Они отделяются от левого края одним символом табуляции. Некоторые версии make гибки в отношении отступов перед командами, но в целом лучше соблюдать один символ табуляции, чтобы make точно смог распознать ее.

Утилита воспринимает каждую строку с отступом под определением цели как отдельную команду. Вы можете добавить столько отступов и команд, сколько захотите. Make будет просто обрабатывать их по очереди.

Есть несколько вещей, которые можно указать перед командами, чтобы заставить make обрабатывать их по-разному:

  • -: дефис перед командой говорит make не прерывать работу в случае ошибки. Например, это может быть полезно, если вы хотите выполнить команду в файле, если она есть, и ничего не делать, если ее нет.
  • @: Если вы выведете команду с символом «@», сам вызов команды не будет отображаться в стандартном выводе. Это в основном используется только для очистки выходных данных.

Дополнительные функции

Makefile-ы поддерживают дополнительные функции.

Переменные

Make распознает переменные (или макросы), которые ведут себя как простые заполнители. Лучше всего объявить их в верхней части файла.

Имя каждой переменной должно полностью состоять из больших букв. После имени ставится знак равенства, который присваивает имя значению с правой стороны. Например, чтобы определить каталог установки /usr/bin, нужно добавить в начало файла:

INSTALLDIR=/usr/bin

Позже в файле можно сослаться на это расположение:

$(INSTALLDIR)

Многострочные команды

Еще одна полезная функция – это возможность команд охватывать несколько строк.

Вы можете использовать любую команду или оболочку в разделе команд. Здесь можно использовать escape-символы новой строки «\»:

target: source
command1 arg1 arg2 arg3 arg4 \
arg5 arg6

Это очень важно, если вы пользуетесь такими функциями оболочки, как if-then:

target: source
if [ "condition_1" == "condition_2" ];\
then\
command to execute;\
another command;\
else\
alternative command;\
fi

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

Если вы собираетесь использовать эту функцию, убедитесь, что у вас нет лишних пробелов или табуляций после «\», иначе вы получите сообщение об ошибке.

Суффиксы файлов

Дополнительная функция, которую вы можете использовать при обработке файлов – это суффиксы файлов. Это общие правила, которые определяют способ обработки файлов на основе их расширения.

Например, если вы хотите обрабатывать все файлы .jpg в каталоге и преобразовывать их в png-файлы с помощью пакета ImageMagick, мы могли бы добавить в Makefile такой код:

jpg .png
.jpg.png:
@echo converting $< to $@
convert $< $@

Первая часть – это объявление .SUFFIXES:. Оно говорит о всех суффиксах, которые вы будете использовать. Некоторые суффиксы, которые часто используются при компиляции исходного кода, такие как «.c» и «.o», включены по умолчанию, их не нужно указывать в объявлении.

Следующая часть – это объявление фактического правила суффикса. Оно в основном имеет такую форму:

original_extension.target_extension:

Это не цель, но она будет соответствовать любому вызову файла со вторым расширением и собирать при этом файлы в первом расширении.

В данном случае можно создать файл под названием «file.png», если в каталоге есть «file.jpg»:

make file.png

Make увидит файл png в объявлении .SUFFIXES и найдет правило для создания файлов «.png». Затем он найдет целевой файл, где вместо «.png» будет «.jpg». После этого утилита выполнит следующие команды.

В правилах суффикса используются некоторые переменные, которые мы еще не вводили. Они помогают заменять различную информацию в зависимости от того, в какой части процесса находятся:

  • $?: Эта переменная содержит список предварительных зависимостей для текущей цели. Это цели, которые необходимо переделать перед выполнением команд в этой цели.
  • $@: Эта переменная является именем текущей цели. Это позволяет ссылаться на файл, который нужно собрать, даже если правило было выбрано шаблоном.
  • $<: Это имя текущей зависимости. В данном случае это имя файла, который используется для создания цели. В этом примере это файл file.jpg.
  • $*: Этот имя текущей зависимости без расширения. По сути, это промежуточный этап между целевым и исходным файлами.

Создание Makefile

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

Чтобы следовать этому мануалу, загрузите инструменты преобразования ImageMagick. Это простые инструменты командной строки для управления изображениями, которые мы будем использовать в скрипте:

sudo apt-get update
sudo apt-get install imagemagick

В текущем каталоге создайте Makefile:

nano Makefile

Преобразование JPG в PNG

Сервер настроен на обслуживание исключительно изображений .png. Потому все .jpg нужно преобразовать в .png перед загрузкой.

Как вы узнали выше, правила суффикса – отличный способ сделать это. Начните с объявления .SUFFIX, где будут перечислены форматы, которые нужно преобразовать.

.SUFFIXES: .jpg .png

Затем можно создать правило, которое превратит файлы .jpg в файлы .png. Чтобы сделать это, используйте команду convert из пакета ImageMagick. Команда convert проста, вот ее синтаксис:

convert from_file to_file

Чтобы выполнить эту команду, нужно правило суффикса, которое определяет исходный и целевой формат:

.SUFFIXES: .jpg .png
.jpg.png:           ## This is the suffix rule declaration

Теперь, когда у вас есть правило, нужно реализовать само преобразование.

Поскольку вы не знаете точно, какое имя будет у файла, нужно использовать переменные, о которых мы говорили ранее. В частности, вам нужно сослаться на исходный файл ($<) и на целевой файл ($@). В итоге получится такое правило:

.SUFFIXES: .jpg .png
.jpg.png:
convert $< $@

Давайте добавим немного функций, чтобы было ясно, что происходит с выражением echo. Добавьте символ «@» перед новой командой и командой, которую вы уже использовали, чтобы команда не отображалась при выполнении:

.SUFFIXES: .jpg .png
.jpg.png:
@echo converting $< to $@ using ImageMagick...
@convert $< $@
@echo conversion to $@ successful!

Сохраните и закройте файл.

В текущий каталог загрузите jpg-файл. Например:

wget https://my-site.com/assets/my-image.jpg
mv my-image.jpg image.jpg

Попробуйте преобразовать его в png:

make image.png
converting image.jpg to image.png using ImageMagick...
conversion to image.png successful!

Утилита make посмотрит в Makefile, найдет там .png в объявлении .SUFFIXES и соответствующее правило. После этого она запустит необходимые команды.

Создание списка файлов

На данный момент make преобразует только файлы, которые вы явно указываете в команде.

Но вы можете создать список файлов jpg, которые находятся в текущем каталоге, а затем преобразовать их в нужный формат. Для этого нужно создать специальную переменную. Это можно сделать с помощью такого синтаксиса wildcard.

JPG_FILES=$(wildcard *.jpg)

Указать цель можно и с помощью подстановочного знака bash:

JPG_FILES=*.jpg

Но этот метод имеет недостаток. Если файлов .jpg нет, эта директива попытается запустить команду преобразования в файле с именем *.jpg, а это не получится сделать.

Синтаксис wildcard, о котором мы говорили выше, компилирует список файлов .jpg в текущем каталоге, и если таких файлов не существует, он не присваивает переменной никакого значения.

При этом нужно попытаться обработать небольшие отличия в .jpg файлах. Некоторые файлы изображений часто отображаются с расширением .jpeg вместо .jpg. Чтобы справиться с этим, можно изменить расширение на .jpg.

Вместо указанных выше строк мы будем использовать эти:

JPEG=$(wildcard *.jpg *.jpeg)     ## Has .jpeg and .jpg files
JPG=$(JPEG:.jpeg=.jpg)            ## Only has .jpg files

Первая строка компилирует список .jpg и .jpeg файлов в текущем каталоге и сохраняет его в переменной JPEG.

Вторая строка ссылается на эту переменную и выполняет простое преобразование имен в переменной JPEG: расширение .jpeg в .jpg. Это делается с помощью этого синтаксиса:

$(VARNAME:.convert_from=.convert_to)

В результате получится новая переменная JPG, содержащая только файлы с расширением .jpg. Некоторые из этих файлов могут фактически не существовать в системе, потому что они являются файлами .jpeg (команда не переименовывает сами файлы). Но это нормально, потому что этот список нужен, чтобы создать новый список .png-файлов:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)

Теперь у вас есть список файлов, которые нужно создать, он находится в переменной PNG. Этот список содержит только .png файлы, потому последняя строка выполнила другое преобразование имени. Все файлы .jpg или .jpeg в этом каталоге использовались для компиляции списка .png-файлов, которые нужно создать.

Также нужно обновить декларацию .SUFFIXES и правило суффиксов, чтобы добавить обработку файлов .jpeg:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png
.jpeg.png .jpg.png:
@echo converting $< to $@ using ImageMagick...
@convert $< $@
@echo conversion to $@ successful!

Создание целей

Makefile уже достаточно большой, но в нем пока что нет целей. Добавьте такую строку, чтобы передать переменную PNG правилу суффиксов.

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png
convert: $(PNG)
.jpeg.png .jpg.png:
@echo converting $< to $@ using ImageMagick...
@convert $< $@
@echo conversion to $@ successful!

Новая цель – это список имен файлов .png, которые выступают в качестве требования. make ищет способ получить файлы .png и использует это правило суффикса.

Теперь можно просто использовать эту команду, чтобы преобразовать все файлы .jpg и .jpeg в.png:

make convert

Давайте добавим еще одну цель. Еще одна задача, которая обычно выполняется при загрузке изображений на сервер – это изменение их размера. Если вы скорректируете размер изображений заранее, вашим пользователям не придется делать это на ходу.

Команда ImageMagick mogrify может изменять размеры изображений. Предположим, область изображения на сайте будет шириной 500 пикселей. Чтобы конвертировать изображения для этой области, используйте команду:

mogrify -resize 500\> file.png

Она изменит размер всех изображений, которые больше этой области, но не затронет изображения, которые меньше 500 пикселей. Добавьте эту цель:

resize: $(PNG)
@echo resizing file...
@mogrify -resize 648\> $(PNG)
@echo resizing is complete!

В файле она будет находиться здесь:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png
convert: $(PNG)
resize: $(PNG)

@echo resizing file...


@mogrify -resize 648\> $(PNG)


@echo resizing is complete!

.jpeg.png .jpg.png:
@echo converting $< to $@ using ImageMagick...
@convert $< $@
@echo conversion to $@ successful!

После этого можно связать эти цели как зависимость следующей цели:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png
webify: convert resize
convert: $(PNG)
resize: $(PNG)
@echo resizing file...
@mogrify -resize 648\> $(PNG)
@echo resizing is complete!
.jpeg.png .jpg.png:
@echo converting $< to $@ using ImageMagick...
@convert $< $@
@echo conversion to $@ successful!

Цель webify теперь преобразует и изменяет размеры изображений.

Загрузка файлов на удаленный сервер

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

Эта цель будет выглядеть примерно так:

upload: webify
scp $(PNG) root@ip_address:/path/to/static/images

Она загрузит все файлы на удаленный сервер. Файл будет выглядеть так:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png
upload: webify

scp $(PNG) root@ip_address:/path/to/static/images

webify: convert resize
convert: $(PNG)
resize: $(PNG)
@echo resizing file...
@mogrify -resize 648\> $(PNG)
@echo resizing is complete!
.jpeg.png .jpg.png:
@echo converting $< to $@ using ImageMagick...
@convert $< $@
@echo conversion to $@ successful!

Чистка

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

clean:
rm *.png

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

Чтобы эта цель работала по умолчанию, нужно указать ее в качестве первой доступной цели. По соглашению ее нужно назвать all:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png
all: upload clean
upload: webify
scp $(PNG) root@ip_address:/path/to/static/images
webify: convert resize
convert: $(PNG)
resize: $(PNG)
@echo resizing file...
@mogrify -resize 648\> $(PNG)
@echo resizing is complete!
clean:
rm *.png
.jpeg.png .jpg.png:
@echo converting $< to $@ using ImageMagick...
@convert $< $@
@echo conversion to $@ successful!

Тепер, зайдя в каталог с файлами Makefile и .jpg или .jpeg, вы можете вызвать make без каких-либо аргументов, чтобы обработать их и отправить на удаленный ваш сервер, а затем удалить загруженные .png файлы.

make

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

Заключение

Теперь вы знакомы с общими функциями Makefile-ов. Также вы знаете, как использовать make как инструмент для автоматизации разных процессов.

Хотя в некоторых случаях удобнее написать скрипт, Makefile – это простой способ настройки структурированной иерархической взаимосвязи между процессами. Это быстрый способ спланировать и объединить повторяющиеся задачи.

Tags: ,