somevar := apple
export somevar
update := $(shell echo \"v=$$somevar\")
all:
@echo $(update)
I was hoping to apple as output of command, however i
The problem is that export
exports the variable to the subshells used by the commands; it is not available for expansion in other assignments. So don't try to get it from the environment outside a rule.
somevar := apple
export somevar
update1 := $(shell perl -e 'print "method 1 $$ENV{somevar}\n"')
# Make runs the shell command, the shell does not know somevar, so update1 is "method 1 ".
update2 := perl -e 'print "method 2 $$ENV{somevar}\n"'
# Now update2 is perl -e 'print "method 2 $$ENV{somevar}\n"'
# Lest we forget:
update3 := method 3 $(somevar)
all:
echo $(update1)
$(update2)
echo $(update3)
perl -e 'print method 4 "$$ENV{somevar}\n"'
Running the makefile
foo:=apple
export foo
all:
@echo ">"$(shell echo "$$foo")
@echo ">""$$foo"
gives for me (with foo undefined in the environment)
$ make
>
>apple
$ make foo=bar
>
>apple
$ export foo=bar; make
>bar
>apple
$ export foo=bar; make foo=bar
>bar
>bar
Try using the quoted form (update := "v=$$somevar"
) and let the shell handle expansion when a command is run (you'll still need the export)
Although export
does not play nicely with $(shell ...)
, there is a simple workaround. We can pass the data to the shell script via the command line.
Now of course, environment passage is robust against issues of escaping and quoting. However, the shell language has a single quote quoting method '...'
which handles everything. The only problem is that there is no way to get a single quote in there; but of course that is solved by terminating the quote, backslash-escaping the needed single quote and starting a new quote: In other words:
ab'cd -> 'ab'\''cd'
In the shell script executed by $(shell ...)
we just generate a variable assignment of the form var='$(...)'
, where $(...)
is some make expression that interpolates suitably escaped material. Thus, Makefile
:
somevar := apple with 'quoted' "stuff" and dollar $$signs
shell_escape = $(subst ','\'',$(1))
update := $(shell v='$(call shell_escape,$(somevar))'; echo $$v > file.txt)
.phony: all
all:
cat file.txt
Sample run:
$ make
cat file.txt
apple with 'quoted' "stuff" and dollar $signs
If we want to communicate an environment variable to a command, we can do that using the shell syntax VAR0=val0 VAR1=val1 ... VARn=valn command arg ...
. This can be illustrated by some minor alterations to the above Makefile
:
somevar := apple with 'quoted' "stuff" and dollar $$signs
shell_escape = $(subst ','\'',$(1))
update := $(shell somevar='$(call shell_escape,$(somevar))' env > file.txt)
.phony: all
all:
grep somevar file.txt
Run:
$ make
grep somevar file.txt
somevar=apple with 'quoted' "stuff" and dollar $signs
file.txt
contains a dump of environment variables, where we can see somevar
. If export
in GNU Make did the right thing, we would have been able to just do:
export somevar
update := $(shell env > file.txt)
but the end result is the same.
Since the end result you want is to echo $(update)
, you would shell_escape
anyway, even if GNU Make passed exported vars to $(shell ...)
. That is to say, look at one more Makefile
:
somevar := apple with 'quoted' "stuff" and dollar $$signs
shell_escape = $(subst ','\'',$(1))
update := $(shell v='$(call shell_escape,$(somevar))'; echo $$v)
.phony: all
all:
@echo '$(call shell_escape,$(update))'
@echo $(update)
Output:
apple with 'quoted' "stuff" and dollar $signs
apple with quoted stuff and dollar
@Beta's answer contains the crucial pointer: with GNU make
, variables marked with export
are only available to [the shells launched for] recipe commands (commands that are part of rules), regrettably not to invocations of $(shell ...)
(they only see the environment that make
itself saw when it was launched).
There is a workaround, however: explicitly pass the exported variable as an environment variable to the shell
function:
update := $(shell somevar='$(somevar)' perl -e 'print "$$ENV{somevar}"')
By prepending the shell command with <var>=<val>
, that definition is added as an environment variable to the environment that the command sees - this is a generic shell feature.
Caveat: @Kaz points out in a comment that this method misbehaves if $(somevar)
contains certain chars., because the variable expansion is verbatim (no escaping), which can break the resulting shell command, and suggests the following variant to also work with embedded '
instances (breaks the input value into single-quoted substrings with quoted '
spliced in):
update := $(shell somevar='$(subst ','\'',$(somevar))' perl -e 'print "$$ENV{somevar}"')
This should work with all values except multi-line ones (which are rare; there is no workaround for multi-line values that I'm aware of).
On a side note, literal $
chars. in values must be represented as $$
, otherwise make
will interpret them as references to its own variables.
Note that I've deliberately NOT chosen the OP's original statement, update := $(shell echo "v=$$somevar")
, for demonstration, because it contains a pitfall that muddles the issue: due to how the shell evaluates a command line, somevar=apple echo v=$somevar
does NOT evaluate to v=apple
, because the $somevar
reference is expanded before somevar=apple
takes effect. To achieve the desired effect in this case, you'd have to use 2 statements: update := $(shell export somevar="$(somevar)"; echo "v=$$somevar")
As for the bug-vs.-feature debate:
While it can be argued that the shell
function should see the same environment as recipe commands, the documentation makes no such promise - see http://www.gnu.org/software/make/manual/make.html#Shell-Function. Conversely, http://www.gnu.org/software/make/manual/make.html#Variables_002fRecursion only mentions making exported variables available to recipe commands.