'Capture standard error of shell in makefile

I have a problem with the shell function in makefiles. Doing this:

var := $(shell some-command)

Allows me to capture the stdout of some-command.

Doing this:

var := $(shell some-command 2>&1)

Allows me to capture the stdout and the stderr of some-command.

How do I capture the stderr of the shell itself?

This makefile ilustrates the problem:

var1 := $(shell date 2>&1)
var2 := $(shell ls aaa 2>&1)
var3 := $(shell a-command-which-does-not-exist 2>&1)

show-me:
    @echo var1: \"$(var1)\"
    @echo var2: \"$(var2)\"
    @echo var3: \"$(var3)\"

It will produce:

/bin/sh: 1: a-command-which-does-not-exist: not found
var1: "So 6. Feb 00:37:05 CET 2022"
var2: "ls: cannot access aaa: No such file or directory"
var3: ""

I would like to capture /bin/sh: 1: a-command-which-does-not-exist: not found into var3. Is this possible?



Solution 1:[1]

You have to run it in a subshell:

var3 := $(shell $(SHELL) -c 'a-command-which-does-not-exist' 2>&1)

There's no other way unfortunately.

ETA OK, I figured it out. The problem is that if the shell invoked by the $(shell ...) function exits with an error code of 127 (which is the shell's exit code for when it can't find a program to run), then GNU make will explicitly write its output to stderr rather than capture it (if you examine the function.c:func_shell_base() function you'll see this).

Any change to the shell that you invoke that ensures it doesn't exit with the 127 exit code will "fix" the problem:

$ cat Makefile
out := $(shell bad-program 2>&1 || exit 1)
$(info out = $out)

$ make
out = /bin/sh: 1: bad-program: not found
make: *** No targets.  Stop.

This might be a bug in GNU make. I'll need to consider it.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1