Variable assignment with substitution - makefile

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.

Related

Passing a command line argument from inside a makefile

I have a makefile from which i am trying to invoke an executable, the executable needs 5 arguments, how do i pass these arguments from makefile
doing this does not works
run-exe:
arg1 = "somevalue"
arg2 = "somevalue"
arg3 = "somevalue"
arg4 = "somevalue"
arg5 = "somevalue"
$(ExeFolderPath)/Task $(arg1) $(arg2) $(arg3) $(arg4) $(arg5)
the arguments are getting ignored.
The commands in a makefile recipe are executed by the shell, not by Make, and each line is executed in a new shell subprocess, so variables defined on previous lines are no longer defined after that line (unless you use .ONESHELL as shown in this answer).
So you cannot set Make variables there, and then use them like $(arg1) later. You need to set shell variables (with no space around the =) and refer to them using shell variable syntax, i.e. $arg1 or ${arg1}, but you need to escape the $ signs so that Make doesn't try to interpret them itself, i.e. use $$arg1 or $${arg1}.
With your attempt you are trying to refer to Make variables called $(arg1) and $(arg2) but those were never defined in the Makefile, so they expand to nothing, and no arguments are passed.
Furthermore, each line of the make recipe is executed in a separate shell process, so the way you wrote it, make creates a new shell subprocess, sets arg1="somevalue" and then that shell process exits (so the variable definition is lost). Then it starts a new shell, sets another variable, then exits that shell etc.
Setting the variables and using them needs to happen in a single shell process, which can be done using .ONESHELL if you are using GNU Make version 3.82 or later. For other versions of Make, another way to ensure that the variable definitions and the uses are all in the same shell process is to put them all on one line:
run-exe:
arg1="somevalue" ; arg2="somevalue" ; arg3="somevalue" ; arg4="somevalue" ; arg5="somevalue" ; $(ExeFolderPath)/Task $$arg1 $$arg2 $$arg3 $$arg4 $$arg5
But this is hard to read, so you can keep them on separate lines but use backslashes so that make still treats them as a single line and runs them in the same shell process:
run-exe:
arg1="somevalue" ; \
arg2="somevalue" ; \
arg3="somevalue" ; \
arg4="somevalue" ; \
arg5="somevalue" ; \
$(ExeFolderPath)/Task $$arg1 $$arg2 $$arg3 $$arg4 $$arg5
Each line in the recipe of the run-exe rule runs in a different shell instance. So, if you set a variable in one line, that assignment won't have any effect for the next line, because that next line runs in a new shell instance.
You can however keep your approach by defining the .ONESHELL-target in order to get all the lines (commands) of a recipe executed in a single shell:
.ONESHELL:
run-exe:
arg1="somevalue"
arg2="somevalue"
arg3="somevalue"
arg4="somevalue"
arg5="somevalue"
$(ExeFolderPath)/Task $$arg1 $$arg2 $$arg3 $$arg4 $$arg5
Note that there shouldn't be spaces around the =, since those variable assignment are run by the shell, not make.
Also note that the $ should be escaped for the shell variables (i.e.: $$ instead of $).
You are going to need GNU Make 3.82 (or newer) in order to use .ONESHELL.

ksh wildcard filename parameter to use in for loop

I am attempting to write a shell script that will take a file name with a wildcard, find all files matching that pattern in current directory, and copy them. My problem is every time I try and use a variable only the first match echo's and thats it.
./copyfiles.ksh cust*.txt
#! /usr/bin/ksh
IN_FILE=${1}
for file in $IN_FILE
do
echo "$file"
done
cust1.txt
This seems to only match the first one even though cust1.txt, cust2.txt, and cust3.txt all exist and when I run it with for file in cust*.txt it works.
The shell expands your argument of "cust*.txt" to a list then passes the list to your script, which then only processes $1 which is cust1.txt.
You want to use $# which will process all arguments passed:
#! /usr/bin/ksh
for file in "$#"
do
echo "$file"
done
I believe there is a limit to how many arguments can be passed this way though. How many files are you having to process? Make sure your version of the shell can handle the number of arguments you are likely to process. If I recall you may need a solution utilizing xargs but I'm a tad rusty to help with that.
In ./copyfiles.ksh cust*.txt the files cust*.txt will be expanded first.
When you do not want to change your copyfiles.ksh script. call it with
./copyfiles.ksh "cust*.txt"
You can also change your script, with something like
IN_FILE="$#" # INFILES would be a better name

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`

Resources