How to use Bash parameter substitution in a Makefile? - bash

I've the following Makefile where I'd like to use Bash parameter substitution syntax as below:
SHELL:=/bin/bash
Foo=Bar
all:
#echo ${Foo}
#echo ${Foo/Bar/OK}
However it doesn't work as expected, as the output of the second echo command is empty:
$ make
Bar
(empty)
Although it works fine when invoking in shell directly:
$ Foo=Bar; echo ${Foo/Bar/OK}
OK
How can I use the above syntax in Makefile?

If you want the shell to expand the variable you have to use a shell variable, not a make variable. ${Foo/Bar/OK} is a make variable named literally Foo/Bar/OK.
If you want to use shell variable substitution you'll have to assign that value to a shell variable:
all:
Foo='$(Foo)'; echo $${Foo/Bar/OK}
Note that we use the double-dollar $$ to escape the dollar sign so that make doesn't try to expand it.
I strongly recommend you don't add # to your rules until you're sure they work. It's the single most common mistake I see; if people would just not use # they could see the command make is invoking, and then they would better understand how make works.

Related

GNU make variable and shell variable in recipe

I'm little confused the make variable and shell variable in recipe.
as the each line of recipe is interpret as the shell, can i do the shell variable assignment?
following is the example:
.ONESHELL:
all:
param="hello"
echo $(param)
//--------------------
no output...
and i know we can use eval to the variable assignment, but it looks as make variable.
how can i just perform the normal shell variable assignment which i want to hold the shell command return value.
Thanks.
$(param) is expanded by GNU make. To make it expanded by the shell do $${param}. Using Variables in Recipes:
Variable and function references in recipes have identical syntax and semantics to references elsewhere in the makefile. They also have the same quoting rules: if you want a dollar sign to appear in your recipe, you must double it (‘$$’). For shells like the default shell, that use dollar signs to introduce variables, it’s important to keep clear in your mind whether the variable you want to reference is a make variable (use a single dollar sign) or a shell variable (use two dollar signs).

Invoking bash commands in makefile [duplicate]

This question already has an answer here:
command substitution doesn't work with echo in makefile [duplicate]
(1 answer)
Closed 6 years ago.
Inside of a makefile, I'm trying to check if fileA was modified more recently than fileB. Using a few prior posts (this and this) as references, I've come up with this as an attempt to store the time since last file modification as a variable:
(I'd rather this be in a function outside of a make recipe, but one step at a time.)
.PHONY: all clean
all : (stuff happens here)
radio :
BASE_MOD_TIME="$( expr $(date +%s) - $(date +%s -r src/radio_interface/profile_init.c) )"
#echo "$(BASE_MOD_TIME)"
I thought that I would be assigning the output of the expr command to a variable, BASE_MOD_TIME, but the output is:
bash-4.1$
BASE_MOD_TIME=""
echo ""
What am I doing wrong here? Simple attempts to save the output of ls -l also didn't work like this.
Make variables are normally global, and you don't normally set make variables in a recipe. A recipe is simply a list of commands to be executed by a shell, and so what looks like a variable assignment in a recipe is a shell variable assignment.
However, each line in a make recipe is run in its own shell subprocess. So a variable set in one line won't be visible in another line; they are not persistent. That makes setting shell variables in recipes less useful. [Note 1]
But you can combine multiple lines of a recipe into a single shell command using the backslash escape at the end of the line, and remembering to terminate the individual commands with semicolons (or, better, link them with &&), because the backslash-escaped newline will not be passed to the shell. Also, don't forget to escape the $ characters so they will be passed to the shell, rather than being interpreted by make.
So you could do the following:
radio:
#BASE_MOD_TIME="$$( expr $$(date +%s) - $$(date +%s -r src/radio_interface/profile_init.c) )"; \
echo "$$BASE_MOD_TIME"; \
# More commands in the same subprocess
But that gets quite awkward if there are more than a couple of commands, and a better solution is usually to write a shell script and invoke it from the recipe (although that means that the Makefile is no longer self-contained.)
Gnu make provides two ways to set make variables in a recipe:
1. Target-specific variables.
You can create a target-specific variable (which is not exactly local to the target) by adding a line like:
target: var := value
To set the variable from a shell command, use the shell function:
target: var := $(shell ....)
This variable will be available in the target recipe and all dependencies triggered by the target. Note that a dependency is only evaluated once, so if it could be triggered by a different target, the target-specific variable might or might not be available in the dependency, depending on the order in which make resolves dependencies.
2. Using the eval function
Since the expansion of recipes is always deferred, you can use the eval function inside a recipe to defer the assignment of a make variable. The eval function can be placed pretty well anywhere in a recipe because its value is the empty string. However, evaling a variable assignment makes the variable assignment global; it will be visible throughout the makefile, but its value in other recipes will depend, again, on the order in which make evaluates recipes, which is not necessarily predictable.
For example:
radio:
$(eval var = $(shell ...))
Notes:
You can change this behaviour using the .ONESHELL: pseudo-target, but that will apply to the entire Makefile; there is no way to mark a single recipe as being executed in a single subprocess. Since changing the behaviour can break recipes in unexpected ways, I don't usually recommend this feature.
What's wrong with this?
fileB: fileA
#echo $< was modified more recently than $#
Instead of forcing the makefile to do all of the heavy lifting via some bash commands, I just called a separate bash script. This provided a lot more clarity for a newbie to bash scripting like myself since I didn't have to worry about escaping the current shell being used by make.
Final solution:
.PHONY: all clean
all : (stuff happens here)
radio :
./radio_init_check.sh
$(MKDIR_P) $(OBJDIR)
make $(radio_10)
with radio_init_check.sh being my sew script.

What does $${var} mean?

What does the $${spkr} mean in the following code, which comes from a Makefile?
for spkr in $(ALLSPKR); do
mkdir -p mgc/$${spkr}
for wav in wav/$${spkr}/$(DATASET)_$${spkr}_*.wav; do
# other stuff
done
done
It means you're reading a shell script embedded in a Makefile. The dollar sign is used by make for its own variable expansion (e.g. $(ALLSPKR) and $(DATASET)). Those variables are expanded before the command is run, and the doubled dollar signs become single dollar signs when the command is run, so the shell sees mkdir -p mgc/${spkr}
If you really needed the PID in a shell script in a Makefile, you'd have $$$$
It's simply a shell variable that's being used inside a Makefile
Using Variables in Recipes

how does this escaping work?

Here is what it finally took to get my code in my makefile to work
Line 5 is the question area
BASE=50
INCREMENT=1
FORMATTED_NUMBER=${BASE}+${INCREMENT}
all:
echo $$((${FORMATTED_NUMBER}))
why do i have to add two $ and two (( )) ?
Formatted_Number if i echo it looks like "50+1" . What is the logic that make is following to know that seeing $$(("50+1")) is actually 51?
sorry if this is a basic question i'm new to make and dont fully understand it.
First, whenever asking questions please provide a complete example. You're missing the target and prerequisite here so this is not a valid makefile, and depending on where they are it could mean very different things. I'm assuming that your makefile is something like this:
BASE=50
INCREMENT=1
FORMATTED_NUMBER=${BASE}+${INCREMENT}
all:
echo $$((${FORMATTED_NUMBER}))
Makefiles are interesting in that they're a combination of two different formats. The main format is makefile format (the first five lines above), but inside a make recipe line (that's the last line above, which is indented with a TAB character) is shell script format.
Make doesn't know anything about math. It doesn't interpret the + in the FORMATTED_NUMBER value. Make variables are all strings. If you want to do math, you have to do it in the shell, in a shell script, using the shell's math facilities.
In bash and other modern shells, the syntax $(( ...expression... )) will perform math. So in the shell if you type echo $((50+1)) (go ahead and try it yourself) it will print 51.
That's why you need the double parentheses ((...)): because that's what the shell wants and you're writing a shell script.
So why the double $? Because before make starts the shell to run your recipe, it first replaces all make variable references with their values. That's why the shell sees 50+1 here: before make started the shell it expanded ${FORMATTED_NUMBER} into its value, which is ${BASE}+${INCREMENT}, then it expanded those variables so it ends up with 50+1.
But what if you actually want to use a $ in your shell script (as you do here)? Then you have to tell make to not treat the $ as introducing a make variable. You do this by doubling it, so if make sees $$ then it does not think that's a make variable, and sends a single $ to the shell.
So for the recipe line echo $$((${FORMATTED_NUMBER})) make actually invokes a shell script echo $((50+1)).
You can use this in BASH:
FORMATTED_NUMBER=$((BASE+INCREMENT))
Is using non BASH use:
FORMATTED_NUMBER=`echo "$BASE + $INCREMENT" | bc`

Variable assignment with substitution

This won't work for me. I want to make some substitution and assign it to a variable in Makefile. An example is as follows but I prefer to do it with Perl since other substitutions can be more complex than this.
eval.%:
# make eval.exp-1.ans
# $* --> exp-1.ans
folder=`echo $* | sed -e 's/\..*//g'`
# OR
folder=`echo $* | perl -ne 'm/(.*)\.ans/; print $$1'
# I want that folder will be exp-1
echo $$folder ${folder}
Why this does not work? How can I do this kind of things in Makefile?
Your question is not clear. Are you trying to set a variable in your makefile, so that other recipes, etc. can see it? Or are you just trying to set a variable in one part of your recipe that can be used in other parts of the same recipe?
Make runs recipes in shells that it invokes. It's a fundamental feature of UNIX that no child process can modify the memory/environment/working directory/etc. of its parent process. So no variable that you assign in a recipe (subshell) of a makefile can ever set a make environment variable. There are ways to do this, but not from within a recipe. However that doesn't appear (from your example) to be what you want to do.
The next thing to note is that make runs each logical line of the recipe in a different shell. So shell variables set in one logical line will be lost when that shell exits, and the next logical line cannot see that value. The solution is to ensure that all the lines of your recipe that need access to the variable are on the same logical line, so they'll be sent to the same shell script, like this:
eval.%:
folder=`echo $* | perl -ne 'm/(.*)\.ans/; print $$1' || exit 1 ; \
echo $$folder
Have you tried the $(VARIABLE:suffix=replacement) syntax? In your case, $(*:.ans=). That will work for any suffix, even if it doesn't start with a dot.

Resources