Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use shell builtin function from a Makefile?

Tags:

shell

makefile

I just stumbled on this problem. I tried to write a very basic Makefile target:

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

The idea is to set up the core size limit to a proper value, make the program crash and then reset the core size limit back to zero. When I am calling this rule, I get the following error message:

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

First, I have been a bit surprised, but I think that the problem is coming from the fact that ulimit is a shell builtin. And, suprisingly (at least for me), these builtin functions cannot be called from a Makefile.

Moreover, ulimit can be both a builtin function only (it is the case on my Debian) or a binary program only (/usr/bin/ulimit).

So, my question is simple, how to workaround this problem, if possible in an elegant and portable manner, and call the builtin function from the inside of a Makefile ?

like image 918
perror Avatar asked Jul 09 '13 11:07

perror


People also ask

How do I run a shell command in makefile?

@anon58192932 That specific iteration, to execute a command, is usually done in a shell syntax fragment in a build recipe, so it occurs in the shell, rather than in make: for x in $(FILES); do command $$x; done . Note the doubled up $$ which passes a single $ to the shell.

What shell is used in makefile?

$(shell) is a special function in gmake that runs an external command and captures the output for use in the makefile.

What does ?= Mean in makefile?

?= indicates to set the KDIR variable only if it's not set/doesn't have a value. For example: KDIR ?= "foo" KDIR ?= "bar" test: echo $(KDIR) Would print "foo"

What is a built in shell command?

In computing, a shell builtin is a command or a function, called from a shell, that is executed directly in the shell itself, instead of an external executable program which the shell would load and execute. Shell builtins work significantly faster than external programs, because there is no program loading overhead.


2 Answers

The reason you're getting this error is that make (GNU make in particular) tries to perform a number of optimizations. One of those is that if the command appears to be a simple command that does not require the shell, then make will simply invoke it directly via fork/exec and not run a shell. If the command only exists as a shell built-in, then this will not work. Your command line ulimit -c 10000 is a simple command and ulimit is not defined as only a shell-builtin that make knows about, so make will try to fork/exec ulimit directly. So a way to get around your immediate issue is to simply add a character that's special to the shell (the most obvious one is ;), which will hint to make that this command needs to be sent to the shell.

However, this will not work for you.

Exactly contrary to H2CO3's comment above: How possibly could it be [a shell builtin], given the functionality it provides? the real question you have to ask yourself is the opposite: how possibly could it NOT be one, given the functionality it provides? The man page for ulimit clearly states: The ulimit utility shall set or report the file-size writing limit imposed on files written by the shell and its child processes, and further: Since ulimit affects the current shell execution environment, it is always provided as a shell regular built-in.

You have to remember that it's virtually impossible for a process in UNIX to modify ANY aspect of its parent process. It can only modify itself, or any child processes that it invokes. This includes the environment variables, working directory, and it also includes ulimit settings.

So, good, how does this apply to your situation? You have to remember that each logical line in a make recipe is invokes in a separate shell. So for a command like:

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

(adding the semicolons to force a shell) what make basically invokes is this:

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

As you can see, each ulimit is invoked in its own shell, so it's effectively useless. It will modify the core file size limit for that shell, then the shell exits and the change is lost, then your program is invoked in a new shell with the original ulimit, then a third shell is invoked and ulimit for cores is set to 0 (also useless).

What you need to do is put all of these commands on a single logical line, like this:

core: myprogram
        ulimit -c 10000; ./myprogram

(you don't need to set the limit back, because the shell will exit anyway).

As a side note, this is why make doesn't worry too much about these shell builtins. A builtin like this is basically impossible to use to any effect in a context where you don't need to use some special shell character like a semicolon.

like image 157
MadScientist Avatar answered Oct 18 '22 09:10

MadScientist


The default shell for any Makefile is sh. If you have to use builtins of a specific shell, specify that shell in your Makefile:

SHELL:=bash

Note that this is bad practice, because the idea is that your Makefile should work on any machine, not only those with bash installed.

If you want to support either variant (external vs. builtin), you can check for the availability of bash resp. ulimit via which, and set a variable containing the command to use (ulimit vs. bash -c "ulimit") depending on the outcome of that check.

Edit: MadScientist is absolutely right about the "current shell only" aspect of ulimit. I'll keep this answer intact for documentation purposes, but it doesn't help with perror's specific problem.

like image 32
DevSolar Avatar answered Oct 18 '22 08:10

DevSolar