1

To embrace the DRY (Don’t Repeat Yourself) principle, I sometimes need to share pieces of shell commands in a Makefile. So there is a recipe somewhere in that file like:

shell=/bin/bash
# …
.ONESHELL:
run:
    # Create bash sub-shell «cmd» var, holding a built-in test as a string
    @cmd='[ $$(grep -iE _dev </etc/hosts | wc -l) -eq 0 ]'
    # "$$" tells make to escape the dollar sign. So echoing …
    @echo "$$cmd"
    # gives «[ $(grep -iE _dev </etc/hosts | wc -l) -eq 0 ]» as I expected
    # I need this variable to be expanded and interpreted as a real
    # built-in square bracket test, so I wrote
    @$$cmd && echo "Pass ! do more magical things …" || true

I expected make to escape $ sign $$cmd$cmd which would in turn be expanded within bash context into the bracket test unquoted string … right ?
But I get an error instead /bin/bash: line 2: [: too many arguments

Does anybody have an idea of why this error is being raised ?
Why bash is not given the bracket test I expect ?
[ $(grep -iE _dev </etc/hosts | wc -l) -eq 0 ] && echo "Pass!"

Thank you.

5
  • I don't speak fluent makefile, but is 'strong quoting' as opposed to "weak quoting" a thing in them?
    – DopeGhoti
    Commented Oct 20, 2017 at 22:41
  • Quoting doesn't exist in make context, quotes are output as is: stackoverflow.com/a/23332194
    – Stphane
    Commented Oct 20, 2017 at 23:00
  • make run prints "Pass ! do more magical things …" with GNU Make 3.81
    – RobertL
    Commented Oct 20, 2017 at 23:06
  • And why would it print "line 2"? Is somehow the newline from wc getting through?
    – RobertL
    Commented Oct 20, 2017 at 23:13
  • @RobertL I'm using Make 4.1, I don't know what it is saying the error accured on line 2. I think dave_thompson_085 got it right.
    – Stphane
    Commented Oct 21, 2017 at 17:53

2 Answers 2

0

Variable and command substitions in the shell occur after deciding on command boundaries, and parsing redirections (and assignments). You can put a program name and/or arguments in a variable and substitute them, but not pipes or redirection or other substitutions including $( command ) or assignments or shell keywords like if and for.

In this case you could eliminate the pipe and wc and substitution by changing the command and reversing your test:

cmd='grep -qi _dev /etc/hosts' # note file as argument not redirection
$$cmd || do_blah

where the substituted grep command fails (silently) if it doesn't find any match in the file, and if it fails do_blah is executed.

In general to use shell syntax (not just program arguments) in a substituted value, you must use eval to execute the substituted value (or values), or else run a child shell like sh -c "$$cmd" (substitute other shell if needed depending on environment and/or command).

0

A Makefile is not a shell script. The entire thing could be more simply and cleanly written as

#!/bin/sh
set -e
grep -q -iE _dev /etc/hosts
echo pass
...
1
  • Sure, make is not bash. You are right, by default make executes each line in its own sub-shell process. But I decorated the target with .ONESHELL directive which tells make to use a single bash process for the entire target recipe, this is how one is able to reference a variable defined on previous line.
    – Stphane
    Commented Oct 21, 2017 at 7:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .