272

I often find Bash syntax very helpful, e.g. process substitution like in diff <(sort file1) <(sort file2).

Is it possible to use such Bash commands in a Makefile? I'm thinking of something like this:

file-differences:
    diff <(sort file1) <(sort file2) > $@

In my GNU Make 3.80 this will give an error since it uses sh instead of bash to execute the commands.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Frank
  • 64,140
  • 93
  • 237
  • 324
  • 1
    This was exactly my problem, took me at least one hour to find this question! I leave my error message here so future readers can find it: `/bin/sh: -c: line 0: syntax error near unexpected token `('` – David Feb 20 '19 at 11:18

7 Answers7

502

From the GNU Make documentation,

5.3.2 Choosing the Shell
------------------------

The program used as the shell is taken from the variable `SHELL'.  If
this variable is not set in your makefile, the program `/bin/sh' is
used as the shell.

So put SHELL := /bin/bash at the top of your makefile, and you should be good to go.

BTW: You can also do this for one target, at least for GNU Make. Each target can have its own variable assignments, like this:

all: a b

a:
    @echo "a is $$0"

b: SHELL:=/bin/bash   # HERE: this is setting the shell for b only
b:
    @echo "b is $$0"

That'll print:

a is /bin/sh
b is /bin/bash

See "Target-specific Variable Values" in the documentation for more details. That line can go anywhere in the Makefile, it doesn't have to be immediately before the target.

Jens
  • 8,423
  • 9
  • 58
  • 78
derobert
  • 49,731
  • 15
  • 94
  • 124
  • 63
    500 bounty waiting for a quote from `man`. Talk about timings. :P – SiddharthaRT Dec 01 '12 at 21:29
  • 3
    @inLoveWithPython Well, `info`, actually, but, I guess it really helped Andy. I know I've had days like that... – derobert Dec 03 '12 at 16:34
  • 3
    if in doubt, @derobert meant literally: `SHELL=/bin/bash` as the first line of the Makefile (or right after the comment). – Yauhen Yakimovich Aug 03 '13 at 21:42
  • 2
    If you use `/usr/bin/env bash` it uses the `bash` in the PATH. But what if it was just `SHELL := bash`? – CMCDragonkai Nov 22 '18 at 04:37
  • One more note to clear some confusion that might happen if you try to `$(info $(SHELL))` from a makefile and see `/bin/bash`: it means nothing! GNU Make inherits the variables from the environment, so the `SHELL` variable comes from the environment, *but* it won't use it when `$(origin SHELL)` is `environment` (my guess). – Johan Boulé Apr 15 '19 at 18:42
  • 1
    The previous comment is not correct. `$(info $(SHELL))` will _always_ show you the shell that make will invoke, not the value of the calling shell's `SHELL` environment variable. If `info` prints `/bin/bash`, that means that somewhere in your makefiles you've set the makefile variable `SHELL` to `/bin/bash`, and that's the shell make will use when invoking recipes. – MadScientist Jun 21 '22 at 17:47
  • use 'SHELL := $(shell which bash)' for portability – ZHOU Ling Oct 08 '22 at 07:51
25

You can call bash directly, use the -c flag:

bash -c "diff <(sort file1) <(sort file2) > $@"

Of course, you may not be able to redirect to the variable $@, but when I tried to do this, I got -bash: $@: ambiguous redirect as an error message, so you may want to look into that before you get too into this (though I'm using bash 3.2.something, so maybe yours works differently).

Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
6

One way that also works is putting it this way in the first line of the your target:

your-target: $(eval SHELL:=/bin/bash)
    @echo "here shell is $$0"
Nicolas Marshall
  • 4,186
  • 9
  • 36
  • 54
4

If portability is important you may not want to depend on a specific shell in your Makefile. Not all environments have bash available.

Menno Smits
  • 2,074
  • 1
  • 13
  • 12
4

You can call bash directly within your Makefile instead of using the default shell:

bash -c "ls -al"

instead of:

ls -al
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
1

There is a way to do this without explicitly setting your SHELL variable to point to bash. This can be useful if you have many makefiles since SHELL isn't inherited by subsequent makefiles or taken from the environment. You also need to be sure that anyone who compiles your code configures their system this way.

If you run sudo dpkg-reconfigure dash and answer 'no' to the prompt, your system will not use dash as the default shell. It will then point to bash (at least in Ubuntu). Note that using dash as your system shell is a bit more efficient though.

Bill G
  • 319
  • 2
  • 7
  • 2
    When invoked under the name `sh`, bash runs in compatibility mode (`set -o posix`). The functionality the OP is trying to use, process substitution, is not available in this mode. – Charles Duffy Jun 23 '17 at 23:55
0

It's not a direct answer to the question, makeit is limited Makefile replacement with bash syntax and it can be useful in some cases (I'm the author)

  • rules can be defined as bash-functions
  • auto-completion feature

Basic idea is to have while loop in the end of the script:

while [ $# != 0 ]; do
  if [ "$(type -t $1)" == 'function' ]; then
    $1 
  else
    exit 1
  fi 
  shift
done

https://asciinema.org/a/435159

Ilya Bystrov
  • 2,902
  • 2
  • 14
  • 21