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

时间:2022-06-06
本文章向大家介绍如何使用Makefile在Ubuntu上自动执行重复任务,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

介绍

如果您有从Linux服务器上的源代码安装软件的经验,您可能会遇到make实用程序。该工具主要用于自动编译和构建程序。它允许应用程序的作者轻松地布置构建该特定项目所需的步骤。

尽管make是为自动化软件编译而创建的,但该工具的设计灵活性足以使其可以自动执行几乎任何可以从命令行完成的任务。在本教程中,我们将讨论如何重新调整make以自动执行按顺序发生的重复性任务。

我们将在Ubuntu上进行演示,但它应该在几乎任何Linux服务器上以类似的方式运行。

安装Make

在我们开始使用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并不突出。

Makefile是特定于目录的,这意味着make将在调用它的目录中搜索以查找这些文件。因此,我们应该将Makefile放在我们将要执行的任务的根目录中,或者调用我们将要编写的脚本最有意义的地方。

在Makefile中,我们遵循特定的格式。以这种方式使用目标,源和命令的概念:

target: source
	command

这种对齐和格式非常重要,因为make很挑剔。我们将在这里讨论每个组件的格式和含义:

target

target是用户指定的名称,用于引用一组命令。可以认为它类似于编程语言中的简单函数。

target在左侧列上对齐,是连续的单词(无空格),以冒号(:)结尾。

调用make时,我们可以通过输入以下内容来指定target:

make target_name

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

source

source是对文件或其他target的引用。它们代表与之关联的目标的准备或依赖关系。

例如,您可以将make文件的一部分看起来像这样:

target1: target2
    target1_command

target2:
    target2_command

在这个例子中,我们可以像这样调用target1:

make target1

然后Make将转到Makefile并搜索“target1”目标。然后它会检查是否有指定的来源。

它会找到“target2”源依赖项并暂时跳转到该目标。

从那里,它将检查target2是否列出了任何来源。它没有,所以它将继续执行“target2命令”。此时,make将到达“target2”命令列表的末尾,并将控制权传递回“target1”目标。然后它将执行“target1命令”并退出。

source可以是文件或目标本身。使用文件时间戳来查看自上次调用以来文件是否已更改。如果已对源文件进行了更改,则重新运行该目标。否则,它将依赖关系标记为已完成并继续到下一个源,或者命令(如果这是唯一的源)。

一般的想法是,通过添加源,我们可以构建一组必须在当前目标之前执行的顺序依赖项。您可以在任何目标之后指定多个以空格分隔的源。您可以开始了解如何指定精细的任务序列。

command

make命令具有这种灵活性的原因是语法的命令部分是非常开放的。您可以指定要在目标下运行的任何命令。您可以根据需要添加任意数量的命令。

命令在目标声明后的行上指定。它们由一个制表符缩进。某些版本的make对于缩进命令部分的方式很灵活,但一般来说,您应该坚持使用单个选项卡以确保make能够识别您的意图。

Make将目标定义下的每个缩进行视为单独的命令。您可以根据需要添加任意数量的缩进行和命令。Make会一次一个地浏览它们。

在命令告诉make以不同的方式处理它们之前,我们可以放置一些东西:

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

附加功能

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

变量

Make识别变量(或宏),它在makefile中作为替换的简单占位符。最好在文件顶部声明这些内容。每个变量的名称都完全大写。在名称后面,等号将名称分配给右侧的值。例如,如果我们想要将安装目录定义为/usr/bin,我们可以在文件顶部添加:

INSTALLDIR=/usr/bin

稍后在文件中,我们可以使用以下语法引用此位置:

$(INSTALLDIR)

跨越多行

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

“”来表示跨行:

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中使用以下内容:

.SUFFIXES: .jpg .png

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

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

下一部分是实际后缀规则的声明。这基本上采取以下形式:

original_extension.target_extension:

这不是一个实际的目标,但它将匹配任何具有第二个扩展名的文件的调用,并在第一个扩展名中将它们构建出文件。

在我们的例子中,如果在我们的目录中有“file.jpg”,我们可以调用make这样构建一个名为“file.png”的文件:

make file.png

Make将在.SUFFIXES声明中看到png文件,并查看用于创建“.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文件的规则。我们可以使用ImageMagick套件中的convert命令来完成此操作。convert命令很简单,其语法是:

convert from_file to_file

要实现此命令,我们需要后缀规则来指定我们开始使用的格式以及我们结束的格式:

.SUFFIXES: .jpg .png

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

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

因为我们不确切知道这里将匹配什么文件名,所以我们需要使用我们学到的变量。具体来说,我们需要引用$<作为原始文件,以及$@作为我们要转换的文件。如果我们将此与我们对convert命令的了解结合起来,我们就会得到以下规则:

.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://ask.qcloudimg.com/raw/qctrain/yehe-b5f4bb2e421e9/1kp7y81up9.jpg
mv digitalocean-badge-blue.jpg badge.jpg

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

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

Make将转到Makefile,请参阅.SUFFIXES声明中的.png,然后转到匹配的后缀规则。然后运行列出的命令。

创建文件列表

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

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

执行此操作的最佳方法是使用wildcard指令:

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添加到后缀列表中,并为我们的规则添加了另一个后缀匹配项。

创建一些Targets

我们现在在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文件名作为要求。然后看看是否有一种方法可以获取.png文件并使用后缀规则来执行此操作。

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

make convert

让我们添加另一个目标。将图像上传到服务器时通常要完成的另一项任务是调整它们的大小。使图像具有正确的大小将使用户无需在请求时动态调整图像大小。

ImageMagick的mogrify命令可以按照我们需要的方式调整图像大小。假设我们的图片将在我们的网站上显示的区域是500px宽。我们可以使用以下命令转换此区域:

mogrify -resize 500> file.png

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

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!

您可能会注意到隐式调整大小将运行与convert相同的命令。我们将指定它们两者,尽管并非总是如此。转换可以在将来包含更精细的处理。

webify目标现在可以转换图像并调整其大小。

将文件上载到远程服务器

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

我们可以通过将转换后的文件列表传递给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

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

为了指定这一点,我们将把它作为第一个可用的目标。这将用作默认值。我们将按惯例称之为“全部”:

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

正如您所看到的,很容易将任务串联在一起,并且可以选择一个流程到某一点。例如,如果您只想转换文件并需要在不同的服务器上托管它们,则可以使用webify目标。

结论


此时,您应该很好地了解如何使用Makefile。更具体地说,您应该知道如何使用make作为自动执行大多数过程的工具。

虽然在某些情况下编写一个简单的脚本可能更容易,但Makefile是在流程之间建立结构化的层次关系的简单方法。学习如何利用这个工具可以帮助简化重复性任务。更多Makefile的教程请前往腾讯云+社区学习更多知识。


参考文献:《How To Use Makefiles to Automate Repetitive Tasks on an Ubuntu VPS》