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.
Best Answer
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 likeif
andfor
.In this case you could eliminate the pipe and wc and substitution by changing the command and reversing your test:
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 likesh -c "$$cmd"
(substitute other shell if needed depending on environment and/or command).