介绍
如果你有从 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 是建立流程之间的结构化、层次性的关系的一种方法,学习如何利用此工具可以帮助使重复性任务更易于管理。