Process substitution in GNU Makefiles

On a bash prompt, one can execute diff using pseudo files:

diff <(echo test) <(echo test)

Adding this as is into a Makefile fails:

all:
        diff <(echo test) <(echo test)

The error (hint: /bin/sh points to /bin/bash on this system):

/bin/sh: -c: line 0: syntax error near unexpected token `('
/bin/sh: -c: line 0: `diff <(echo test) <(echo test)'

What does it mean, and is there a way to still diff two outputs without using temporary files?

Asked By: Johannes

||

/bin/sh may be bash on your system, but when invoked as sh, bash will be running in POSIX mode (as if POSIXLY_CORRECT was defined, or it was started with --posix).

In this mode, process substitutions do not exist.

Solutions:

  1. Use explicit temporary files:

    all:
        command1 >tmpfile
        command2 | diff tmpfile -
        rm -f tmpfile
    
  2. Use a bash -c in-line script:

    all:
        bash -c 'diff <(command1) <(command2)'
    
  3. Define the Makefile variable SHELL as /bin/bash (or whatever the path to bash is on your system):

    SHELL=/bin/bash
    

If you want portability, go with the first solution. If you are OK with a dependency on bash, pick the second. If you additionally don’t need to care about non-GNU make implementations, use the third.


Regarding setting SHELL: The POSIX standard says that executables in Makefiles should be invoked with the system() C library function by make. This function is not guaranteed to use the SHELL environment variable (in fact, doing so is discouraged by the standard). The standard also goes to some length to say that setting the Makefile variable SHELL should not affect the environment variable SHELL. In most implementations of make that I know of, however, the Makefile variable SHELL will be used to execute the commands.

The suggestion in the Rationale for the make utility is to use bash -c:

The historical MAKESHELL feature, and related features provided by other make implementations, were omitted. In some implementations it is used to let a user override the shell to be used to run make commands. This was confusing; for a portable make, the shell should be chosen by the makefile writer. Further, a makefile writer cannot require an alternate shell to be used and still consider the makefile portable. While it would be possible to standardize a mechanism for specifying an alternate shell, existing implementations do not agree on such a mechanism, and makefile writers can already invoke an alternate shell by specifying the shell name in the rule for a target; for example:

python -c "foo"

Answered By: Kusalananda