0

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.

  • I don't speak fluent makefile, but is 'strong quoting' as opposed to "weak quoting" a thing in them? – DopeGhoti Oct 20 '17 at 22:41
  • Quoting doesn't exist in make context, quotes are output as is: stackoverflow.com/a/23332194 – Stphane Oct 20 '17 at 23:00
  • make run prints "Pass ! do more magical things …" with GNU Make 3.81 – RobertL Oct 20 '17 at 23:06
  • And why would it print "line 2"? Is somehow the newline from wc getting through? – RobertL Oct 20 '17 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 Oct 21 '17 at 17:53
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
...
  • 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 Oct 21 '17 at 7:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.