如何使用 Makefile 自动执行重复任务

介绍

如果你有从 Linux 服务器上安装源代码的软件的经验,那么你可能已经遇到制造工具,这个工具主要用于编译和构建程序,它允许源代码的作者列出构建该特定项目所需的步骤。

虽然 make 是为了自动化软件编译而设计的,但该工具是以足够灵活的方式设计的,可以用来自动化从命令行完成的几乎任何任务. 在本指南中,我们将讨论如何重用 make 来自动化连续发生的重复任务。

前提条件

任何类型的Linux环境都会为本教程工作. 包安装说明是为Ubuntu/Debian Linux和Red Hat/Rocky Linux提供的。

安装做

大多数Linux发行版允许您使用单个命令安装编译器,但不默认提供一个命令。

在Ubuntu上,您可以安装一个名为build-essential的包,它将为现代、支持良好的编译环境提供所需的所有包。

1sudo apt update
2sudo apt install build-essential

在 Rocky Linux 或其他 Red Hat 衍生品中,您可以安装一组名为 Development Tools 的包,以提供相同的编译器功能。

1dnf groups mark install "Development Tools"
2dnf groupinstall "Development Tools"

您可以通过检查系统上是否存在制造命令来验证编译器是否可用。

1which make
1[secondary_label Output]
2/usr/bin/make

现在你有工具,将允许你利用制造在其通常的容量也。

了解化妆品

使命令接收指令的主要方式是通过使用Makefile

Makefiles 是目录特定的,这意味着 make 会搜索目录,在那里它被召唤来找到这些文件. 我们应该把 Makefile 放在我们要执行的任何任务的根部,或者在那里叫我们要写的脚本最有意义。

在Makefile中,我们遵循一个特定的格式,Make以以下方式使用目标、源和命令的概念:

1[label Makefile]
2target: source
3    command

这些组件的对齐和格式非常重要,我们将在这里讨论每个组件的格式和含义:

目标

目标是一个用户指定的名称,指向一组命令. 将其视为一个编程语言中的函数。

目标位于左列上,是一个连续的单词(没有间隙),并以结肠(:)结束。

在调用 make 时,我们可以通过键入指定目标:

1make target_name

Make 将检查 Makefile 并执行与该目标相关的命令。

来源

源是对文件或其他目标的引用,它们代表了它们所关联的目标的先决条件或依赖。

例如,你可能有你的Makefile的一个部分看起来像这样:

1[label Makefile]
2target1: target2
3    target1_command
4
5target2:
6    target2_command

在这个例子中,我们可以这样称呼 target1:

1make target1

然后,Make会去Makefile并搜索target1目标,然后检查是否有任何指定的来源。

它会找到target2的源依赖性,并暂时跳到该目标。

从那里,它会检查 target2 是否有任何来源列出。它没有,所以它会继续执行 target2_command. 在此时, make 会到达 target2 命令列表的尽头,并将控制传回 target1 目标。

源可以是文件或目标本身。Make 使用文件时刻标记来查看文件是否自上次召唤以来发生了更改。如果对源文件进行了更改,则该目标重新运行。

一般的想法是,通过添加源,我们可以构建一组连续的依赖性,必须在当前目标之前执行。

命令

使命令如此灵活的是,语法的命令部分非常开放,您可以指定任何命令在目标下运行,您可以根据需要添加多少命令。

命令在目标声明之后在行中指定。它们由一个 tab 字符注入。某些版本的 make 是灵活的,你如何注入命令部分,但一般来说,你应该坚持一个单一的标签,以确保这个 make 会识别你的意图。

Make 将目标定义下的每个注入行视为单独的命令. 您可以添加您想要的尽可能多的注入行和命令。

有几个事情,我们可以放置在命令告诉使以不同的方式处理之前:

  • -:一个命令之前的冲击表示如果遇到错误,请不要使命令中断。例如,如果您想在文件中执行命令,如果它存在,则可以使用它,而如果它没有,则不用执行任何操作。

附加特性

一些额外的功能可以帮助您在 Makefile 中创建更复杂的规则链。

变量

Make 识别变量(或宏),这些变量(或宏)在你的 makefile 中作为替代的位置持有者行为,最好在文件顶部声明这些变量。

每个变量的名称都被完全资本化。 名称之后,一个平等符号将名称分配给右侧的值。 例如,如果我们想将安装目录定义为 /usr/bin,我们可以在文件顶部添加 INSTALLDIR=/usr/bin

后来在文件中,我们可以使用$(INSTALLDIR)语法来引用这个位置。

逃离新闻

我们可以做的另一个有用的事情是允许命令跨多个行。

我们可以在命令部分中使用任何命令或壳功能,包括通过用 \ 结束行来逃避新行字符:

1[label Makefile]
2target: source
3    command1 arg1 arg2 arg3 arg4 \
4    arg5 arg6

如果您利用壳中一些更具编程性的功能,如 if-then 语句,这变得更加重要:

1[label Makefile]
2target: source
3    if [ "condition_1" == "condition_2" ];\
4    then\
5        command to execute;\
6        another command;\
7    else\
8        alternative command;\
9    fi

事实上,我们可以把它写成一行,但它大大提高了可读性,这样打破了它。

如果你要逃避行字符的尽头,请确保在\之后没有任何额外的空间或卡,否则你会收到一个错误。

文件足够规则

您可以使用的文件处理的另一个功能是文件延伸. 这些是提供基于其扩展的方式处理文件的一般规则。

例如,如果您想在一个目录中处理所有.jpg 文件,并使用 ImageMagick 套件将其转换为.png 文件,我们可以在我们的 Makefile 中拥有类似的东西:

1[label Makefile]
2.SUFFIXES: .jpg .png
3
4.jpg.png:
5    @echo converting $< to $@
6    convert $< $@

这里我们需要看看一些事情。

第一部分是.SUFFIXES:声明,这说明我们将在文件声明中使用的所有声明中一些声明,通常用于编译源代码的声明,如.c.o文件是默认包含的,不需要在声明中标记。

接下来的部分是实际延伸规则的声明,它以original_extension.target_extension:的形式进行。

这不是一个实际的目标,但它将与第二个扩展相匹配,并在第一个扩展中将它们从文件中构建出来。

在我们的情况下,我们可以称之为,以构建一个名为file.png的文件,如果我们的目录中有一个file.jpg:

1make file.png

「make」會在「.SUFFIXES」宣言中找到 png 檔案,並看到建立「.png」檔案的規則,然後會在目錄中搜尋目標檔案,並用「.png」取代「.jpg」。

补充规则使用一些我们尚未介绍的变量,这些变量有助于取代基于该过程的哪个部分的不同信息:

  • $?: 此变量包含对当前目标的依赖性列表,这些比目标更新的依赖性列表。 这些是必须在执行该目标下的命令之前重新完成的目标。
  • $@: 此变量是当前目标的名称。 这使我们能够引用您正在尝试创建的文件,即使这个规则通过模式匹配。
  • $<: 这是当前依赖性的名称。 在补充规则的情况下,这是用于创建目标的文件名称。 在我们的例子中,这将包含 file.jpg
  • $*: 此文件是当前依赖性名称,与匹配的扩展被删除。 考虑到目标和源文件之间的中间阶段。

创建一个转换 Makefile

我们将创建一个Makefile,它会进行一些图像操作,然后将文件上传到我们的文件服务器,以便我们的网站可以显示它们。

如果你想遵循,在你开始之前,确保你有ImageMagick包安装. 这些是操作图像的命令行工具,我们将在我们的脚本中使用它们。

在Ubuntu或Debian上,更新您的包源并用apt安装:

1sudo apt-get update
2sudo apt-get install imagemagick

在Red Hat或Rocky上,您需要添加epel-release复制程序以获取类似此类的额外包,然后使用dnf安装包:

1dnf install epel-release
2dnf install ImageMagick

在当前目录中,创建一个名为Makefile的文件:

1nano Makefile

在此文件中,我们将开始实施我们的转换目标。

将所有 JPG 文件转换为 PNG

我们的服务器已设置为专门服务.png 图像. 因此,我们需要在上传之前将任何.jpg 文件转换为.png。

正如我们在上面所了解的那样,一个字符串规则是这样做的一个很好的方法. 我们将从.SUFFIX声明开始,该声明将列出我们正在转换的格式: .SUFFIXES:.jpg.png

之后,我们可以创建一个规则,将.jpg 文件更改为.png 文件. 我们可以使用 ImageMagick 套件的 convert 命令来做到这一点. 转换命令语法是 convert from_file to_file

要执行此命令,我们需要补丁规则,该规则指定我们开始的格式和我们结束的格式:

1[label Makefile]
2.SUFFIXES: .jpg .png
3
4.jpg.png:           ## This is the suffix rule declaration

现在,我们有将匹配的规则,我们需要实施实际的转换步骤。

因为我们不知道这里将匹配哪个文件名,我们需要使用我们所了解的变量. 具体地说,我们需要将 $< 作为原始文件,而 $@ 作为我们正在转换的文件。

1[label Makefile]
2.SUFFIXES: .jpg .png
3
4.jpg.png:
5    convert $< $@

让我们添加一些功能,以便我们可以明确地告诉我们在响应声明中发生了什么。我们将在新命令和我们已经拥有的命令之前添加@符号,以便在执行时使实际命令不被打印:

1[label Makefile]
2.SUFFIXES: .jpg .png
3
4.jpg.png:
5    @echo converting $< to $@ using ImageMagick...
6    @convert $< $@
7    @echo conversion to $@ successful!

在此时刻,我们应该保存并关闭文件,以便我们可以测试它。

将 jpg 文件放入当前目录. 如果您没有文件,您可以从 DigitalOcean 网站下载一个文件,使用 wget:

1wget https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.png
2mv DO_Powered_by_Badge_blue.png badge.jpg

您可以通过请求它创建一个 badge.png 文件来测试你的 make 文件是否工作:

1make badge.png
1[secondary_label Output]
2converting badge.jpg to badge.png using ImageMagick...
3conversion to badge.png successful!

Make 将进入 Makefile,在 .SUFFIXES 声明中查看.png,然后进入匹配的补丁规则,然后运行列出的命令。

创建一个文件列表

在此时刻,可以创建.png 文件,如果我们明确地告诉它我们想要该文件。

如果它只是在当前目录中创建了一个.jpg 文件列表,然后将这些文件转换,我们可以通过创建一个变量来做到这一点,该变量将所有我们的文件转换。

最好的方法是使用 wildcard 指令,例如 JPG_FILES=$(wildcard *.jpg)

我們可以只用 bash wildcard 指定目標,例如 JPG_FILES=*.jpg,但這有一個缺點. 如果沒有.jpg 檔案,這實際上試圖在名為 *.jpg 的檔案上執行轉換指令,這將失敗。

我们上面提到的 wildcard 语法编译了当前目录中的.jpg 文件列表,如果没有,它不会将变量设置为任何东西。

当我们这样做时,我们应该尝试处理常见的.jpg 文件中的轻微变异. 这些图像文件通常用.jpeg 扩展而不是.jpg. 为了以自动的方式处理这些,我们可以在我们的程序中更名为.jpg 文件。

取而代之的是上述两条,我们将使用以下两条:

1[label Makefile]
2JPEG=$(wildcard *.jpg *.jpeg)     ## Has .jpeg and .jpg files
3JPG=$(JPEG:.jpeg=.jpg)            ## Only has .jpg files

第一行编译了当前目录中的.jpg 和.jpeg 文件列表,并将它们存储在名为JPEG的变量中。

第二行引用此变量,并进行名称翻译,将以.jpeg 结尾的 JPEG 变量中的名称转换为以.jpg 结尾的名称。

在这两个行的末尾,我们将有一个名为JPG的新变量,它只包含.jpg 文件名。其中一些文件可能并不存在于系统上,因为它们实际上是.jpeg 文件(没有真正的更名发生)。

1[label Makefile]
2JPEG=$(wildcard *.jpg *.jpeg)
3JPG=$(JPEG:.jpeg=.jpg)
4PNG=$(JPG:.jpg=.png)

现在,我们有一个列表的文件,我们想要请求的变量 PNG. 这个列表只包含.png 文件名,因为我们做了另一个名称转换. 现在,这个目录中的每个文件都是.jpg 或.jpeg 文件已经被用来编译一个列表的.png 文件,我们想要创建。

我们还需要更新.SUFFIXES声明和延伸规则,以反映我们现在正在处理.jpeg 文件:

 1[label Makefile]
 2JPEG=$(wildcard *.jpg *.jpeg)
 3JPG=$(JPEG:.jpeg=.jpg)
 4PNG=$(JPG:.jpg=.png)
 5.SUFFIXES: .jpg .jpeg .png
 6
 7.jpeg.png .jpg.png:
 8    @echo converting $< to $@ using ImageMagick...
 9    @convert $< $@
10    @echo conversion to $@ successful!

正如你所看到的,我们已将.jpeg 添加到补丁列表中,并为我们的规则添加了另一个补丁匹配。

创建一些目标

我们现在在我们的Makefile中有很多,但我们还没有任何正常的目标,让我们修复它,以便我们可以将我们的PNG列表传递到我们的延伸规则:

 1[label Makefile]
 2JPEG=$(wildcard *.jpg *.jpeg)
 3JPG=$(JPEG:.jpeg=.jpg)
 4PNG=$(JPG:.jpg=.png)
 5.SUFFIXES: .jpg .jpeg .png
 6
 7convert: $(PNG)
 8
 9.jpeg.png .jpg.png:
10    @echo converting $< to $@ using ImageMagick...
11    @convert $< $@
12    @echo conversion to $@ successful!

所有这一新的目标是列出我们收集的.png 文件名称作为一个要求,然后看看是否有方法可以获取.png 文件,并使用口号规则来做到这一点。

现在,我们可以使用这个命令将所有.jpg 和.jpeg 文件转换为.png 文件:

1make convert

让我们添加另一个目标. 通常在上传图像到服务器时完成的另一个任务是调整大小。

一个名为mogrify的 ImageMagick 命令可以改变我们需要的图像大小. 假设我们的图像将在我们的网站上显示的区域宽为 500px. 我们可以用命令转换这个区域:

1mogrify -resize 500\> file.png

这将调整任何大于500px宽的图像大小以适应该区域,但不会触及更小的图像。

1[label Makefile]
2resize: $(PNG)
3    @echo resizing file...
4    @mogrify -resize 648\> $(PNG)
5    @echo resizing is complete!

我们可以将其添加到我们的文件中,如下:

 1[label Makefile]
 2JPEG=$(wildcard *.jpg *.jpeg)
 3JPG=$(JPEG:.jpeg=.jpg)
 4PNG=$(JPG:.jpg=.png)
 5.SUFFIXES: .jpg .jpeg .png
 6
 7convert: $(PNG)
 8
 9resize: $(PNG)
10    @echo resizing file...
11    @mogrify -resize 648\> $(PNG)
12    @echo resizing is complete!
13
14.jpeg.png .jpg.png:
15    @echo converting $< to $@ using ImageMagick...
16    @convert $< $@
17    @echo conversion to $@ successful!

现在,我们可以将这两个目标连接在一起,作为另一个目标的依赖:

 1[label Makefile]
 2JPEG=$(wildcard *.jpg *.jpeg)
 3JPG=$(JPEG:.jpeg=.jpg)
 4PNG=$(JPG:.jpg=.png)
 5.SUFFIXES: .jpg .jpeg .png
 6
 7webify: convert resize
 8
 9convert: $(PNG)
10
11resize: $(PNG)
12    @echo resizing file...
13    @mogrify -resize 648\> $(PNG)
14    @echo resizing is complete!
15
16.jpeg.png .jpg.png:
17    @echo converting $< to $@ using ImageMagick...
18    @convert $< $@
19    @echo conversion to $@ successful!

您可能会注意到,重量默认地将运行与转换相同的命令. 我们将指定两者,但在这种情况下并不总是如此。

Webify 目标现在可以转换和重置图像。

将文件上传到远程服务器

现在我们已经为网络准备了我们的图像,我们可以创建一个目标,将它们上传到我们的服务器上的静态图像目录。

我们的目标将看起来像这样的东西:

1[label Makefile]
2upload: webify
3    scp $(PNG) root@ip_address:/path/to/static/images

這將將我們所有的檔案上傳到遠端伺服器. 我們的檔案現在看起來像這樣:

 1[label Makefile]
 2JPEG=$(wildcard *.jpg *.jpeg)
 3JPG=$(JPEG:.jpeg=.jpg)
 4PNG=$(JPG:.jpg=.png)
 5.SUFFIXES: .jpg .jpeg .png
 6
 7upload: webify
 8    scp $(PNG) root@ip_address:/path/to/static/images
 9
10webify: convert resize
11
12convert: $(PNG)
13
14resize: $(PNG)
15    @echo resizing file...
16    @mogrify -resize 648\> $(PNG)
17    @echo resizing is complete!
18
19.jpeg.png .jpg.png:
20    @echo converting $< to $@ using ImageMagick...
21    @convert $< $@
22    @echo conversion to $@ successful!

清洁起来

让我们添加一个清理选项来删除所有本地.png 文件,然后将它们上传到远程服务器:

1[label Makefile]
2clean:
3    rm *.png

现在,我们可以在顶部添加另一个目标,在我们将我们的文件上传到远程服务器后称之为这个目标。

要明确这一点,我们将把它作为第一个可用的目标。这将被用作默认值。我们将按惯例称之为全部:

 1[label Makefile]
 2JPEG=$(wildcard *.jpg *.jpeg)
 3JPG=$(JPEG:.jpeg=.jpg)
 4PNG=$(JPG:.jpg=.png)
 5.SUFFIXES: .jpg .jpeg .png
 6
 7all: upload clean
 8
 9upload: webify
10    scp $(PNG) root@ip_address:/path/to/static/images
11
12webify: convert resize
13
14convert: $(PNG)
15
16resize: $(PNG)
17    @echo resizing file...
18    @mogrify -resize 648\> $(PNG)
19    @echo resizing is complete!
20
21clean:
22    rm *.png
23
24.jpeg.png .jpg.png:
25    @echo converting $< to $@ using ImageMagick...
26    @convert $< $@
27    @echo conversion to $@ successful!

通过这些最后的触摸,如果您输入Makefile和.jpg 或.jpeg 文件的目录,您可以呼叫 make 没有任何争议来处理您的文件,将其发送到您的服务器,然后删除您上传的.png 文件。

1make

正如你所看到的,可以将任务连接在一起,并在某个时刻选择一个过程,例如,如果你只想转换你的文件,并且需要在不同的服务器上托管它们,你可以使用webify目标。

结论

在这一点上,你应该有一个很好的想法如何使用Makefiles一般. 更具体地说,你应该知道如何使用Make作为一个工具来自动化大多数类型的程序。

虽然在某些情况下,编写脚本可能更有效,但Makefiles 是建立流程之间的结构化、层次性的关系的一种方法,学习如何利用此工具可以帮助使重复性任务更易于管理。

Published At
Categories with 技术
comments powered by Disqus