如何使用Makefiles自动化重复的任务

简介 jiè)

如果您在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

 

Output

/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以不同方式处理它们。

  • -: A dash before a command tells make to not abort if an error is encountered. For instance, this could be useful if you want to execute a command on a file if it is present, and do nothing if it is not.
  • @: If you lead a command with the @ symbol, the command call itself will not be printed to standard output. This is mainly used just to clean up the output that make produces.

其他特点

一些额外的功能可以帮助你在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

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

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

文件后缀规则

你可以用于文件处理的额外功能是文件后缀。这些是一般规则,根据文件的扩展名来处理文件的方式。

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

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

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

我们需要看一些东西。

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

接下来是实际后缀规则的声明。它的形式是原始扩展名.目标扩展名:。

这不是一个真实的目标,但它将匹配任何由第二个扩展名呼叫的文件,并根据第一个扩展名的文件进行构建。

在我们的情况下,如果在我们的目录中有一个文件.jpg,我们可以像这样调用make来构建一个名为file.png的文件。

  1. make file.png

 

在”.SUFFIXES”声明中,make将查找.png文件并查看创建.png文件的规则。然后,在目录中寻找以.jpg替换.png后缀的目标文件。接下来,它会执行后续的命令。

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

  • $?: This variable contains the list of dependencies for the current target that are more recent than the target. These would be the targets that must be re-done before executing the commands under this target.
  • $@: This variable is the name of the current target. This allows us to reference the file you are trying to make, even though this rule was matched through a pattern.
  • $<: This is the name of the current dependency. In the case of suffix rules, this is the name of the file that is used to create the target. In our example, this would contain file.jpg
  • $*: This file is the name of the current dependency with the matched extension stripped off. Consider this an intermediate stage between the target and source files.

创建一个转换 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格式。

就像我们之前学到的那样,后缀规则是一种很好的方法。我们将以.SUFFIX声明开始,其中会列出我们要转换的格式:.SUFFIXES: .jpg .png。

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

为了执行这个指令,我们需要一个后缀规则,该规则指定了我们要开始的格式和要结束的格式。

生成文件
.SUFFIXES: .jpg .png

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

既然我们有了能够匹配的规则,我们需要实施实际的转换步骤。

因为我们不知道在这里会匹配到什么文件名,所以我们需要使用我们学到的变量。具体而言,我们需要用 $< 指代原始文件,用 $@ 指代我们要转换的文件。如果我们将这个规则与我们对 convert 命令的了解相结合,我们就得到了以下规则:

构建文件
.SUFFIXES: .jpg .png

.jpg.png:
    convert $< $@

让我们添加一些功能,以便我们可以明确地了解回显语句中正在发生的事情。我们将在新命令之前加上@符号,并将已经有的命令包含其中,以在执行时使实际命令不被打印出来:

创建文件
.SUFFIXES: .jpg .png

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

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

将一个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

 

您可以测试您的Makefile是否正常工作,通过要求它创建一个badge.png文件。

  1. make badge.png

 

Output

converting badge.jpg to badge.png using ImageMagick… conversion to badge.png successful!

会去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)     ## 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!

正如你所见,我们已将 “.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是一种建立进程之间结构化、层次化关系的方式。学习如何利用这个工具可以帮助更轻松地处理重复性任务。

发表回复 0

Your email address will not be published. Required fields are marked *