如何使用Makefile自动执行UbuntuVPS上的重复任务
介绍
如果您有在 Linux 服务器上从源代码安装软件的经验,您可能遇到过 make
实用程序。 该工具主要用于自动化程序的编译和构建。 它允许应用程序的作者轻松布置构建该特定项目所需的步骤。
尽管 make 是为了自动化软件编译而创建的,但该工具以足够灵活的方式设计,它可以用于自动化您可以从命令行完成的几乎所有任务。 在本指南中,我们将讨论如何重新利用 make 来自动执行按顺序发生的重复性任务。
我们将在 Ubuntu 12.04 VPS 上演示这一点,但它应该在几乎所有 Linux 服务器上以类似的方式运行。
安装制作
在我们开始使用 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 并没有那么突出。
Makefiles 是特定于目录的,这意味着 make 将在调用它的目录中搜索以查找这些文件。 因此,我们应该将 Makefile 放在我们将要执行的任何任务的根目录中,或者放在最适合调用我们将编写的脚本的位置。
在 Makefile 中,我们遵循特定的格式。 Make 以这种方式使用目标、源和命令的概念:
target: source command
this 的对齐和格式非常重要,因为 make 很挑剔。 我们将在这里讨论每个组件的格式和含义:
目标
目标是用户指定的名称,用于引用一组命令。 把它想象成类似于编程语言中的一个简单函数。
目标在左侧列上对齐,是一个连续的单词(没有空格),并以冒号 (:) 结尾。
调用 make 时,我们可以通过键入以下内容来指定目标:
制作目标名称
然后 Make 将检查 Makefile 并执行与该目标相关的命令。
来源
源是对文件或其他目标的引用。 它们代表与它们关联的目标的先决条件或依赖关系。
例如,您的 make 文件的一部分可能如下所示:
target1: target2 target1_command target2: target2_command
在这个例子中,我们可以像这样调用 target1:
make target1
然后 Make 将转到 Makefile 并搜索“target1”目标。 然后它会检查是否有任何指定的来源。
它将找到“target2”源依赖项并临时跳转到该目标。
从那里,它将检查 target2 是否列出了任何来源。 它没有,所以它会继续执行“target2_command”。 此时,make 将到达“target2”命令列表的末尾并将控制权交还给“target1”目标。 然后它将执行“target1_command”并退出。
源可以是文件或目标本身。 Make 使用文件时间戳来查看文件自上次调用以来是否已更改。 如果对源文件进行了更改,则重新运行该目标。 否则,它将依赖项标记为已完成并继续到下一个源,或者如果那是唯一的源,则继续执行命令。
一般的想法是,通过添加源,我们可以构建一组顺序的依赖项,这些依赖项必须在当前目标之前执行。 您可以在任何目标之后指定多个源,以空格分隔。 您可以开始了解如何指定详细的任务序列。
命令
使 make 命令如此灵活的原因是语法的命令部分非常开放。 您可以指定您希望在目标下运行的任何命令。 您可以根据需要添加任意数量的命令。
命令在目标声明之后的行中指定。 它们由一个 tab 字符缩进。 某些版本的 make 对命令部分的缩进方式很灵活,但一般来说,您应该坚持使用单个制表符以确保 make 能够识别您的意图。
Make 将目标定义下的每个缩进行视为一个单独的命令。 您可以根据需要添加任意数量的缩进行和命令。 Make 将简单地一次通过它们。
我们可以在命令之前放置一些东西来告诉 make 以不同的方式处理它们:
- -:命令前的破折号告诉 make 如果遇到错误则 not 中止。 例如,如果您想在文件存在时对文件执行命令,如果文件不存在则不执行任何操作,这可能很有用。
- @:如果您以“@”符号引导命令,则命令调用本身不会打印到标准输出。 这主要用于清理 make 产生的输出。
附加的功能
一些附加功能可以帮助您在 Makefile 中创建更复杂的规则链。
变量
Make 识别变量(或宏),它们在您的 makefile 中充当简单的占位符,用于替换。 最好在文件顶部声明这些。
每个变量的名称完全大写。 在名称之后,等号将名称分配给右侧的值。 例如,如果我们想将安装目录定义为 /usr/bin
,我们可以在文件顶部添加:
INSTALLDIR=/usr/bin
在文件的后面,我们可以使用以下语法引用此位置:
$(INSTALLDIR)
转义换行符
我们可以做的另一件有用的事情是允许命令跨越多行。
我们可以在命令部分使用任何命令或 shell 功能。 这包括通过以“”结束行来转义换行符:
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”文件默认包含在内,不需要在此声明中标记。
下一部分是实际后缀规则的声明。 这基本上采取以下形式:
原始扩展名。 目标扩展:
这不是一个实际的目标,但它将匹配对具有第二个扩展名的文件的任何调用,并从第一个扩展名中的文件构建它们。
在我们的例子中,如果我们的目录中有“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 命令很简单,它的语法是:
将from_file 转换为_file
为了实现这个命令,我们需要指定我们开始的格式和结束的格式的后缀规则:
.SUFFIXES: .jpg .png .jpg.png: ## This is the suffix rule declaration
现在我们有了匹配的规则,我们需要实现实际的转换步骤。
因为我们并不确切知道这里将匹配什么文件名,所以我们需要使用我们所了解的变量。 具体来说,我们需要引用 $<
作为原始文件,并将 $@
作为我们要转换到的文件。 如果我们将其与我们对 convert 命令的了解结合起来,我们会得到以下规则:
.SUFFIXES: .jpg .png .jpg.png: convert $< $@
让我们添加一些功能,以便明确告诉我们 echo 语句发生了什么。 我们将在新命令和我们已经拥有的命令之前包含“@”符号,以使实际命令在执行时不被打印:
.后缀:.jpg .png .jpg.png: @回声使用 ImageMagick 将 $< 转换为 $@... @转变 $< $@ @回声转换为 $@ 成功!
此时,我们应该保存并关闭文件,以便我们可以测试它。
获取一个jpg文件到当前目录。 如果您手头没有文件,可以通过键入以下内容从 DigitalOcean 网站获取文件:
wget https://digitalocean.com/assets/v2/badges/digitalocean-badge-blue.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 文件列表然后转换它们会更好。 我们可以通过创建一个变量来保存我们要转换的所有文件来做到这一点。
最好的方法是使用通配符指令:
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 文件的 new 列表:
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
列表传递给我们的后缀规则:
JPEG=$(通配符 *.jpg *.jpeg) JPG=$(JPEG:.jpeg=.jpg) PNG=$(JPG:.jpg=.png) .SUFFIXES: .jpg .jpeg .png 转换:$(PNG) .jpeg.png .jpg.png: @回声使用 ImageMagick 将 $< 转换为 $@... @转变 $< $@ @回声转换为 $@ 成功!
这个新目标所做的只是列出我们作为要求收集的 .png 文件名。 然后,Make 会查看是否有一种方法可以获取 .png 文件并使用后缀规则来执行此操作。
现在,我们可以简单地使用此命令将所有 .jpg 和 .jpeg 文件转换为 .png 文件:
make convert
让我们添加另一个目标。 将图像上传到服务器时通常完成的另一项任务是调整它们的大小。 让您的图像具有正确的尺寸将使您的用户不必在他们请求时动态调整图像大小。
一个名为 mogrify
的 ImageMagick 命令可以按照我们需要的方式调整图像大小。 假设我们的图像将在我们的网站上显示的区域是 500 像素宽。 我们可以使用以下命令对此区域进行转换:
mogrify -resize 500\> file.png
这将调整任何大于 500 像素宽的图像以适应该区域,但不会触及较小的图像。 这就是我们想要的。 作为目标,我们可以添加以下规则:
调整大小:$(PNG) @回声调整文件大小… @mogrify -调整大小 648> $(PNG) @回声调整大小完成!
我们可以像这样将它添加到我们的文件中:
JPEG=$(通配符 *.jpg *.jpeg) JPG=$(JPEG:.jpeg=.jpg) PNG=$(JPG:.jpg=.png) .SUFFIXES: .jpg .jpeg .png 转换:$(PNG) 调整大小:$(PNG) @回声调整文件大小… @mogrify -调整大小 648> $(PNG) @回声调整大小完成! .jpeg.png .jpg.png: @回声使用 ImageMagick 将 $< 转换为 $@... @转变 $< $@ @回声转换为 $@ 成功!
现在,我们可以将这两个目标串在一起作为另一个目标的依赖项:
JPEG=$(通配符 *.jpg *.jpeg) JPG=$(JPEG:.jpeg=.jpg) PNG=$(JPG:.jpg=.png) .SUFFIXES: .jpg .jpeg .png webify:转换调整大小 转换:$(PNG) resize: $(PNG) @echo 调整文件大小... @mogrify -resize 648> $(PNG) @echo 调整大小完成! .jpeg.png .jpg.png: @回声使用 ImageMagick 将 $< 转换为 $@... @转变 $< $@ @回声转换为 $@ 成功!
您可能会注意到,resize 隐式将运行与 convert 相同的命令。 我们将同时指定它们,但情况并非总是如此。 转换可能在未来包含更精细的处理。
webify 目标现在可以转换和调整图像大小。
上传文件到远程服务器
现在我们已经为网络准备好了图像,我们可以创建一个目标来将它们上传到我们服务器上的静态图像目录。 我们可以通过将转换后的文件列表传递给 scp
来做到这一点:
我们的目标看起来像这样:
上传:webify scp $(PNG) root@ ip_address : /path/to/static/images
这会将我们所有的文件上传到远程服务器。 我们的文件现在看起来像这样:
JPEG=$(通配符 *.jpg *.jpeg) JPG=$(JPEG:.jpeg=.jpg) PNG=$(JPG:.jpg=.png) .SUFFIXES: .jpg .jpeg .png 上传:webify scp $(PNG) root @ip _address:/path/to/static/images webify:转换调整大小 转换:$(PNG) resize: $(PNG) @echo 调整文件大小... @mogrify -resize 648> $(PNG) @echo 调整大小完成! .jpeg.png .jpg.png: @回声使用 ImageMagick 将 $< 转换为 $@... @转变 $< $@ @回声转换为 $@ 成功!
清理
让我们添加一个清理选项,以在将所有本地 .png 文件上传到远程服务器后删除它们:
清理:rm *.png
现在,我们可以在顶部添加另一个目标,在我们将文件上传到远程服务器后调用这个目标。 这将是最完整的目标,也是我们想要默认的目标。
为了指定这一点,我们将把它作为第一个可用的目标。 这将用作默认值。 按照惯例,我们将其称为“全部”:
JPEG=$(通配符 *.jpg *.jpeg) JPG=$(JPEG:.jpeg=.jpg) PNG=$(JPG:.jpg=.png) .SUFFIXES: .jpg .jpeg .png 全部:上传干净 上传:webify scp $(PNG) root@ip_address:/path/to/static/images webify:转换调整大小 转换:$(PNG) resize: $(PNG) @echo 调整文件大小... @mogrify -resize 648> $(PNG) @echo 调整大小完成! 干净:rm *.png .jpeg.png .jpg.png: @回声使用 ImageMagick 将 $< 转换为 $@... @转变 $< $@ @回声转换为 $@ 成功!
通过这些最后的修改,如果您进入包含 Makefile 和 .jpg 或 .jpeg 文件的目录,您可以不带任何参数调用 make 来处理您的文件,将它们发送到您的服务器,然后删除您上传的 .png 文件。
make
如您所见,将任务串在一起很容易,也很容易挑选到某个点的流程。 例如,如果您只想转换文件并且需要将它们托管在不同的服务器上,您可以只使用 webify 目标。
结论
此时,您应该对如何使用 Makefile 有一个很好的了解。 更具体地说,您应该知道如何使用 make 作为自动化大多数过程的工具。
虽然在某些情况下编写简单的脚本可能更容易,但 Makefile 是一种在进程之间建立结构化、层次结构关系的简单方法。 学习如何利用此工具有助于简化重复性任务。