What you probably missed is that the eval
parameter is expanded by make. So, in:
$(eval MY_IP=$(kubectl describe service hello-node -n default | grep "LoadBalancer Ingress:" | awk '{print $$3}'))
the $(kubectl describe...)
is expanded by make as the empty string (unless you have a make variable named kubectl describe...
, which is unlikely). The eval
function thus assigns the empty string to the make variable MY_IP
. It does not happen with back ticks but here you hit another problem: the MY_IP
variable is assigned:
`kubectl describe... | awk '{print $3}'`
not the result of its evaluation by the shell. Note that the $$3
has been transformed in $3
by make when it expanded the eval
parameter. And it is when executing:
echo IP: $(MY_IP)
that make expands (recursively) the recipe that becomes, step by step:
echo IP: `kubectl describe... | awk '{print $3}'`
and then:
echo IP: `kubectl describe... | awk '{print }'`
(unless you have a make variable named 3
). This is what is passed to the shell, leading to what you see. Use $$$$3
, instead, and it should work as you expect... except that the make variable MY_IP
is not assigned the value you wanted.
The shell
function, as you noticed, solves all this but you end up with a terrible mixture: a shell command, evaluated by make through the shell
function, its result assigned to a make variable thanks to the eval
make function, in the middle of a recipe which is a list of shell commands. I don't know what you want to do but there must be something simpler.
For instance, if you used a make variable just because you couldn't pass a shell variable from one line of your recipe to the next (normal, they are executed by two independent shell invocations), you could reduce your recipe to a single line (but with ; \
line continuation for better readability):
deploy.runtime:
@MY_IP=`kubectl describe... | awk '{print $$3}'`; \
echo IP: $$MY_IP
Here, MY_IP
is a shell variable and it is still available to the echo
command because it is part of the same recipe line. Note the double $
sign in $$3
and $$MY_IP
to escape the first expansion by make.
If, instead, you really want to use a make variable, you could assign it as a regular make variable (using back ticks or the shell
function, as you wish):
MY_IP = `kubectl describe... | awk '{print $$3}'`
deploy.runtime:
@echo IP: $(MY_IP)
or:
MY_IP = $(shell kubectl describe... | awk '{print $$3}')
deploy.runtime:
@echo IP: $(MY_IP)
Important note about this last solution:
The MY_IP = ...
assignment is a recursive (delayed) assignment, instead of a simple (immediate) assignment MY_IP := ...
. This means that the shell command is not expanded and executed immediately when you invoke make. It is only when make needs the value of MY_IP
. So, if the deploy.runtime
recipe is the only place where there is a reference to the value of MY_IP
, the kubectl...
will be executed only if this recipe is executed. For instance, if you have another clean
target which recipe does not use the value of MY_IP
, and if you invoke make clean
, the kubectl...
will not be executed at all. The drawback is that if you have several places where make needs its value, the kubectl...
will be executed several times.
If you use the simple assignment, instead:
MY_IP := $(shell kubectl describe... | awk '{print $$3}')
the kubectl
command will be executed only once but it will be each time you invoke make, even if its value is not needed.
In case this is a problem, Mad Scientist has a very nice trick to combine the pros and cons of recursive and simple assignments:
MY_IP = $(eval MY_IP := $$(shell kubectl...))$(MY_IP)
If you are interested, read his post about this. How many $
you will need for the awk
command is left as an exercise...