如何在Makefile中使用shell内置函数?

10

我刚遇到这个问题。我尝试编写一个非常基本的Makefile目标:

core: myprogram
        ulimit -c 10000
        ./myprogram
        ulimit -c 0

这个想法是将核心大小限制设为适当的值,使程序崩溃,然后将核心大小限制重置为零。当我调用此规则时,会收到以下错误消息:

$ make core
cc -Wall -Wextra -m32 -g  -o example example.c 
ulimit -c 100000
make: ulimit: Command not found
make: *** [core] Error 127

首先,我有些惊讶,但我认为问题出在 ulimit 是一个 shell 内置命令的事实上。而且,令人惊讶的是(至少对我来说),这些内置函数不能从 Makefile 中调用。

此外,ulimit 可以是仅限于内置函数(在我的 Debian 系统上就是这种情况)或者仅限于二进制程序(例如:/usr/bin/ulimit)。

那么,我的问题很简单,如何解决这个问题,如果可能的话,在一个优雅且可移植的方式下,从 Makefile 中调用内置函数?


不,ulimit不是shell内置命令。(考虑到它提供的功能,它怎么可能是一个内置命令呢?) - user529758
那我为什么有一个/usr/bin/ulimit可执行文件呢? - user529758
这是两件不同的事情。就像test或其他一些东西一样。许多内置函数会复制一个二进制程序。但是,这是首先使用的内置函数。 - perror
5个回答

25
你遇到错误的原因是make(特别是GNU make)尝试执行多个优化操作之一。其中之一是,如果该命令似乎是不需要shell的简单命令,则make将直接通过fork/exec调用它,而不运行shell。如果该命令只作为shell内置命令存在,则这种方法将无效。你的命令行“ulimit -c 10000”是一个简单的命令,并且ulimit并没有定义为make知道的仅是Shell内置命令,因此make将尝试直接fork/exec ulimit。因此,解决你的问题的一种方法是简单地添加一个对Shell(最明显的是“;”)有特殊含义的字符,这将提示make需要将此命令发送给Shell。
但是,这对你来说行不通。
与H2CO3上面的评论恰恰相反:鉴于它提供的功能,它怎么可能是[一个shell内置命令]?你必须问自己相反的问题:鉴于它提供的功能,它怎么可能不是一个shell内置命令?ulimit的man页面清楚地说明了:ulimit实用程序设置或报告由shell和其子进程写入的文件强制执行的文件大小限制,并进一步指出:由于ulimit影响当前的shell执行环境,因此它始终作为shell常规内置提供。 你必须记住,在UNIX中,一个进程几乎不可能修改其父进程的任何方面。它只能修改自身或调用的任何子进程。这包括环境变量、工作目录以及ulimit设置。
那么,好的,这如何适用于你的情况呢?你必须记住,make配方中的每个逻辑行都在单独的shell中调用。所以对于像下面这样的命令:
core: myprogram
        ulimit -c 10000 ;
        ./myprogram
        ulimit -c 0 ;

(添加分号以强制运行shell)基本上make调用的是这个:

core: myprogram
        /bin/sh -c 'ulimit -c 10000 ;'
        /bin/sh -c './myprogram'
        /bin/sh -c 'ulimit -c 0 ;'

正如您所看到的,每个ulimit都在自己的shell中调用,因此它实际上是无用的。 它将修改该shell的核心文件大小限制,然后该shell退出并且更改会丢失,然后使用原始ulimit调用您的程序,然后又调用第三个shell并将cores的ulimit设置为0(同样无用)。

您需要做的是将所有这些命令放在单个逻辑行上,像这样:

core: myprogram
        ulimit -c 10000; ./myprogram

(你不需要将限制设置回来,因为shell无论如何都会退出)。

顺便提一下,这就是为什么make不太担心这些shell内置命令。像这样的内置命令基本上不可能在不需要使用某些特殊的shell字符(比如分号)的情况下发挥任何作用。


@perror:他说得对,你知道的...我只是考虑了通用的“内置 vs. 外部”问题,而没有具体考虑 ulimit 的影响。这应该是被接受的答案。 - DevSolar
亲爱的MadScientist,这真是一个非常出色的答案。而且,这也正是我所说的“优雅”的意思。非常感谢! - perror
谢谢,你节省了我的时间。 - Gasol
谢谢,"你需要做的是将所有这些命令放在单个逻辑行上"听起来像Dockerfile中的层级结构。 - Kevin Chan

3

任何Makefile的默认shell都是sh。如果您必须使用特定shell的内置命令,请在您的Makefile中指定该shell:

SHELL:=bash

请注意,这是不好的做法,因为想法是你的 Makefile 应该在任何计算机上运行,而不仅仅是安装了 bash 的计算机。
如果您想支持外部或内置变量,可以通过 which 检查 bash 或 ulimit 的可用性,并根据该检查结果设置包含要使用的命令(ulimit vs. bash -c "ulimit")的变量。
编辑:MadScientist 关于 ulimit 的“当前 shell only”方面是绝对正确的。我会保留这个答案以供文档目的,但它对 perror 的具体问题没有帮助。

@perror:您可以通过which检查bashulimit的可用性,并根据结果设置包含要使用的命令(ulimit vs. bash -c "ulimit")的变量。 - DevSolar
是的,我会尝试这个方法。但是,对我来说它似乎有点像暴力破解的解决方案。真的没有希望做出更优雅的东西吗? :-) - perror
@perror:这并不是真正的暴力方法。考虑一下:你在sh环境中。有两种方法可以得到你想要的结果,一个是通过可执行文件bash,另一个是通过可执行文件ulimit。你必须检查哪个可用...没有比使用which更简洁的方法了(which就是为这个目的而建立的工具)。 - DevSolar
好吧,“brute-force”可能不太合适,但对我来说枚举这种情况还是有点冒险的。我们永远不知道是否会有新的情况出现并破坏一切。 - perror
但是,似乎又出现了另一个问题。当我尝试应用该规则时,出现了以下错误:/bin/bash: line 0: ulimit: core file size: cannot modify limit: Operation not permitted。是否需要通过setuid获取root权限才能使用ulimit - perror
显示剩余2条评论

2
您可以说:
core: myprogram
        $(shell /bin/bash -c "ulimit -c 10000")
        ./myprogram
        $(shell /bin/bash -c "ulimit -c 0")

在命令行中,$shell()包装器真的必要吗?它不是用于变量赋值吗? - DevSolar
抱歉,这会强制使用 /bin/bash,我需要一个既适用于具有二进制 ulimit 又适用于内置 ulimit 的系统的解决方案... 再次抱歉。 - perror
有比使用 $(shell ...) 更简单(且更便携)的方法来强制执行 shell,但无论如何这都不会有所帮助。请查看我的答案。 - MadScientist
@MadScientist 我同意,所提出的方法更像是一种hack。 - devnull
这个有问题!Make 将整个 shell 命令块作为单个递归展开的变量存储。在决定运行该块的时候,它会将其展开。此时,both $(shell ...) 行都被展开了。现在 make 只剩下一个字符串 ./myprogram,并将其传递给 shell(在 both ulimit 命令运行之后)。 - bobbogo

0
在GNU make官方文档(http://www.gnu.org/software/make/manual/make.html)的第5.3.2章节中提到:“作为shell使用的程序取自SHELL变量。如果在您的makefile中未设置此变量,则使用程序/bin/sh作为shell。” 您可以将SHELL设置为/bin/bash。

-1
简短回答:在命令的末尾添加一个分号(;)。这样,您将调用带有内置工具的完整 shell。
        ulimit -c 10000;

替代

        ulimit -c 10000

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接