轻松掌握Makefile:自动化重复任务,提升工作效率

简介

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

尽管make工具是为了自动化软件编译而创建的,但它的设计十分灵活,几乎可以用来自动化通过命令行完成的任何任务。在本指南中,我们将讨论如何重新使用make来自动化连续发生的重复任务。

先决条件

任何类型的Linux环境都适用于本教程。针对Ubuntu/Debian Linux和Red Hat/Rocky Linux都提供了软件包安装说明。

安装Make

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

在Ubuntu上,您可以安装一个称为build-essential的软件包,它将提供现代、得到良好支持的编译器环境所需的所有软件包。使用apt更新您的软件源并安装该软件包。

  1. sudo apt update
  2. sudo apt install build-essential

在Rocky Linux或其他Red Hat衍生版本中,您可以安装一组名为开发工具的软件包,以提供相同的编译器功能。使用dnf安装这些软件包。

  1. dnf groups mark install “Development Tools”
  2. dnf groupinstall “Development Tools”

通过检查系统上是否存在make命令,您可以证实编译器是否可用。为了做到这一点,请使用which命令。

  1. which make
输出
/usr/bin/make

现在你有了工具,可以利用它通常的能力,从中受益。

理解Makefiles

使用Makefile是make命令接收指令的主要方式。

Makefile是目录特定的,这意味着make命令会在调用它的目录中搜索这些文件。我们应该将Makefile放在我们要执行任务的根目录中,或者放在最合理调用我们编写的脚本的位置。

在Makefile中,我们遵循特定的格式。Make使用目标、源文件和命令的概念来实现这一点。

制作文件

target: source
    command

对齐和格式非常重要。我们将在此讨论每个组成部分的格式和含义。

目标

目标是用户指定的名称,用于指代一组命令。可以将其视为编程语言中的函数。

目标位于左边的栏中,是一个连续的单词(没有空格),并以冒号(:)结尾。

在调用make时,我们可以通过输入来指定一个目标。

  1. make target_name

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

来源(源)

来源是对文件或其他目标的引用。它们代表了与其关联目标的前提条件或依赖关系。

举个例子,你可以在你的Makefile中有一个部分看起来像这样:

构建文件

target1: target2
    target1_command

target2:
    target2_command

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

  1. make target1

那么,Make指令将导航到Makefile文件并搜索target1目标。接下来,它会检查是否指定了任何源文件。

它将寻找目标2的源依赖并临时跳转到该目标。

从那里开始,它会检查target2是否有任何列出的来源。它没有,所以它会继续执行target2_command。在这一点上,make会到达target2命令列表的末尾,并将控制权交回给target1目标。然后,它会执行target1_command并退出。

来源可以是文件或目标本身。使用文件时间戳来判断自上次运行以来文件是否发生了更改。如果有源文件发生了变化,那么需要重新运行该目标。否则,将依赖标记为已满足,并继续处理下一个源文件或命令(如果那是唯一的源文件)。

总的想法是通过添加资源,我们可以构建一系列的依赖关系,这些依赖关系必须在当前目标之前执行。在任何目标之后,你可以指定一个以上的资源,用空格分隔开。你可以开始看到如何指定复杂的任务序列。

指令

使make命令如此灵活的原因是它的语法中的命令部分非常开放。你可以指定任何要在目标下运行的命令。你可以添加任意多个命令。

目标声明之后,命令会在下一行上进行具体说明。命令部分需要使用一个制表符进行缩进。某些版本的 make 命令在命令部分的缩进方式上较为灵活,但通常来说,为了确保 make 命令能够正确识别您的意图,建议使用单一制表符进行缩进。

Make将目标定义下的每个缩进的行都视为一个单独的命令。您可以添加任意数量的缩进行和命令,Make会逐一执行它们。

有几个事情我们可以在命令前放置,告诉make以不同方式处理它们。

  • :命令前的破折号告诉make在遇到错误时不要中止。例如,如果您想在文件存在时对其执行命令,而在文件不存在时不执行任何操作,这可能会很有用。
  • @:如果您在命令前使用@符号,命令调用本身将不会打印到标准输出。这主要用于清理make产生的输出。

其他特点

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

变量

在你的makefile中,make会识别变量(或宏),它们作为占位符来进行替换。最好在文件的开头声明这些变量。

每个变量的名称都采用完全大写的形式。在名称后面,等号将该名称赋值给右侧的数值。例如,如果我们想将一个安装目录定义为/usr/bin,我们可以在文件顶部添加INSTALLDIR=/usr/bin。

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

避免使用换行符

还有一项有用的功能是允许命令跨越多行。

在命令部分中,我们可以使用任何命令或shell功能。这包括通过在行末尾使用\来转义换行符。

Makefile的含义是一个文本文件,其中包含了用于编译和构建软件项目的规则和命令。

target: source
    command1 arg1 arg2 arg3 arg4 \
    arg5 arg6

如果你充分利用一些shell的更多程序化功能,例如if-then语句,这将变得更加重要。

生成文件

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

这将按照一行命令的方式执行此代码块。实际上,我们可以将其写成一行,但是像这样将其分解会显著提高可读性。

如果你要转义换行字符,请确保在\之后没有任何额外的空格或制表符,否则会出现错误。

文件后缀规则

这是文章《如何使用Makefiles自动化重复的任务》的第2部分(共4部分)。

在文件处理方面,你可以利用的额外功能是文件后缀规则。这些通用规则根据文件的扩展名来定义处理方式。

例如,如果你想处理一个文件夹中的所有.jpg文件,并使用ImageMagick工具套件将它们转换为.png文件,我们可以在Makefile中编写类似以下内容:

Makefile 是一种用于自动化编译和构建程序的文本文件。
.SUFFIXES: .jpg .png

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

我们需要了解几个关键部分。首先是.SUFFIXES声明,它告诉make我们将要使用的所有文件后缀。一些常用于编译源代码的后缀,如.c和.o文件,默认情况下已经包含在内,不需要在此声明中标记。

接下来是实际后缀规则的声明,其格式为”原始扩展名.目标扩展名:”。这并不是一个真实的目标,但它会匹配任何以第二个扩展名结尾的文件,并根据第一个扩展名的文件进行构建。

在我们的例子中,如果目录中有一个名为file.jpg的文件,我们可以通过以下方式调用make来构建名为file.png的文件:

  1. make file.png

在.SUFFIXES声明中,make会查找.png文件并查看用于创建.png文件的规则。然后,在目录中寻找将.png后缀替换为.jpg后缀的目标文件。接着,它会执行相应的命令。

后缀规则使用了一些我们尚未介绍过的变量。这些变量有助于根据当前流程的不同部分进行信息替换。

  • $?: 此变量包含比当前目标更新的所有依赖项列表。这些是在执行此目标下的命令之前必须重新构建的目标。
  • $@: 此变量是当前目标的名称。它允许我们引用你正在尝试构建的文件,即使此规则是通过模式匹配的。
  • $<: 这是当前依赖项的名称。在后缀规则的情况下,这是用于创建目标的文件名。在我们的示例中,这将包含file.jpg。
  • $*: 这是当前依赖项去掉匹配扩展名后的文件名。可以将其视为目标和源文件之间的中间阶段。

创建一个转换Makefile

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

如果您想跟随操作,请在开始前确保已安装ImageMagick软件包。ImageMagick是用于处理图像的命令行工具,我们将在脚本中使用它们。

在Ubuntu或Debian系统上,更新您的软件源并使用apt安装:

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

在Red Hat或Rocky系统上,您需要添加epel-release仓库以获取额外的软件包,然后使用dnf安装该软件包:

  1. dnf install epel-release
  2. dnf install ImageMagick

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

  1. nano Makefile

在这个文件中,我们将开始编写转换目标。

将所有JPG文件转换为PNG格式

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

正如我们之前所学,后缀规则是一种很好的方法。我们将以.SUFFIXES声明开始,其中列出我们要转换的格式:.SUFFIXES: .jpg .png

然后,我们可以制定一项规则,将.jpg文件转换为.png文件。我们可以使用ImageMagick工具套件中的convert命令来实现此操作。convert命令的语法是:convert 源文件 目标文件。

为了执行此指令,我们需要一个后缀规则,该规则指定我们要转换的源格式和目标格式。

生成文件
.SUFFIXES: .jpg .png

.jpg.png:           ## 这是后缀规则声明

既然我们有了能够匹配的规则,我们需要实施实际的转换步骤。由于我们不知道将会匹配到什么文件名,因此需要使用我们学到的变量。具体来说,我们需要用 $< 指代原始文件,用 $@ 指代我们要转换的文件。如果我们将这个规则与对convert命令的了解结合起来,就得到以下规则:

构建文件
.SUFFIXES: .jpg .png

.jpg.png:
    convert $< $@

让我们添加一些功能,以便通过回显语句明确了解正在发生的事情。我们将在新命令前加上@符号,并对已有命令也做同样处理,这样在执行时就不会打印实际命令:

创建文件
.SUFFIXES: .jpg .png

.jpg.png:
    @echo 正在使用ImageMagick将 $< 转换为 $@...
    @convert $< $@
    @echo 成功转换为 $@!

此时,我们应该保存并关闭文件以便测试。将一个jpg文件下载到当前目录中。如果你手头没有文件,可以使用wget从Silicon Cloud网站下载一个:

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

您可以通过要求创建badge.png文件来测试Makefile是否正常工作:

  1. make badge.png
输出

使用ImageMagick将badge.jpg转换为badge.png… 转换为badge.png成功!

会去Makefile中找到”.SUFFIXES”声明中的.png,然后找到与之匹配的后缀规则。然后运行列出的命令。

创建一个文件列表

在这个点上,如果我们明确告诉Make我们想要一个.png文件,它可以创建它。

如果它只创建当前目录中的.jpg文件列表,然后将它们转换,会更好。我们可以通过创建一个变量来保存要转换的所有文件来实现这一点。

用通配符指令最好的方法是使用JPG_FILES=$(wildcard *.jpg)

我们可以只用bash通配符指定一个目标,比如JPG_FILES=*.jpg,但这有一个缺点。如果没有.jpg文件,它实际上会尝试在一个名为*.jpg的文件上运行转换命令,这将失败。

我们上面提到的通配符语法编译了当前目录中的.jpg文件列表,如果没有文件存在,它就不会设置变量。

在进行这个过程中,我们应该尝试处理一种常见的.jpg文件的轻微变化。这些图像文件经常以.jpeg扩展名而不是.jpg出现。为了以自动化的方式处理它们,我们可以在我们的程序中将它们的名称更改为.jpg文件。

我们将使用下面这两行,而不是上面的那两行。

制作文件

JPEG=$(wildcard *.jpg *.jpeg)     ## 包含.jpeg和.jpg文件
JPG=$(JPEG:.jpeg=.jpg)            ## 只包含.jpg文件

第一行编译当前目录中的.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!

正如你所见,我们已将 “.jpeg” 添加到后缀列表,并为规则新增了另一个后缀匹配项。

制定一些目标

我们现在的Makefile里面有相当多内容,但是还没有任何正常的目标。让我们解决这个问题,这样我们就可以把PNG列表传递给我们的后缀规则。

Makefile 是一个文本文件,用于指导计算机程序的编译和构建过程。

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文件:

  1. make convert

 

让我们增加另一个目标。通常在将图像上传到服务器时,另一个任务是调整图像大小。确保图像大小正确将使您的用户无需在请求时即时调整图像大小。

一个叫做mogrify的ImageMagick命令可以按照我们的需求调整图片大小。假设我们网站上显示图片的区域宽度为500像素,我们可以使用以下命令来转换图片:

  1. mogrify -resize 500\> file.png

 

这将调整任何宽度大于500像素的图像以适应该区域,但不会触及较小的图像。这正是我们想要的。作为目标,我们可以添加如下规则:

Makefile – 创建文件

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!

你可能会注意到,resize会隐式地运行与convert相同的命令。尽管如此,我们将同时指定两者,以防万一不总是这种情况。convert在将来可能会包含更复杂的处理。

现在,Webify目标可以将图片转换并调整大小。

上传文件至远程服务器

现在我们已经为网页准备好了图片,我们可以创建一个目标,将它们上传到服务器上的静态图片目录中。我们可以通过将转换后的文件列表传递给scp来实现这一目标。

我们的目标将会是这样的:

Makefile

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

这将把我们所有的文件上传到远程服务器。我们的文件现在的样子是这样的:

Makefile制造文件

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文件。

Makefile -> 构建文件

clean:
    rm *.png

现在,我们可以在顶部添加另一个目标,在将文件上传到远程服务器后调用此目标。这将是最完整的目标,并且是我们想要设置为默认的目标。

为了明确指定此项,我们将将其作为最首要的目标放置。这将作为默认选项使用。按照惯例,我们将统一称之为。

Makefile 可以被改写为 “构建文件”,用于控制和自动化程序编译和构建的过程。

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文件。

  1. make

 

正如你所看到的,我们可以将任务串联起来,也可以选择在某个特定的点上进行任务选择。举个例子,如果你只想将文件转换并且需要将它们存储在另一个服务器上,你可以只使用webify目标。

结论是到此为止,你应该对如何使用Makefiles有了一个很好的理解。更具体地说,你应该知道如何使用make作为自动化大多数过程的工具。

在某些情况下,编写脚本可能效果更好,但Makefile是一种建立进程之间结构化、层次化关系的方式。学习如何利用这个工具可以帮助更轻松地处理重复性任务。

bannerAds