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).
Related
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.
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.
I have noticed a bit of a strange behaviour of $# in CSH. For example
set a=(Hello world); echo $#a
will output 2 as expected.
Why does $# act differently with combination of environment variables?
echo $#PATH
will actually output PATH ignoring # I would expect it to output 1.
My guess is that this is just one of those CSH quirks. Nothing in man page about this. Can somebody explain this behaviour? (Please don't bother writing "don't use CSH" comments I wouldn't use it if I didn't have to)
$#foo, where $foo is a shell array variable, yields the number of elements in the array $foo. If $foo is an ordinary non-array variable, $#foo yields 1.
csh and tcsh treat shell variables (set by the set command) quite differently from environment variables (set by the setenv command or inherited from the parent process). The value of an environment variable is always just a string (which is why $PATH, for example, needs : characters to delimit the list elements).
It would probably be more consistent for $#FOO, where $FOO is an environment variable, to yield 1 rather than expanding to the value of $FOO. In any case, the behavior doesn't seem to be documented, and you should avoid relying on it.
If you specifically want the number of directories in your path, $#path will give you that; $path is a shell array variable that's automatically tied to the value of the $PATH environment variable.
For other variables, you just have to keep track of whether a variable is a shell variable or an environment variable.
Environment variables can't be a list, only variables can, so doing $#PATH makes littles sense. It should probably be an error.
If you want to get the length of the path, you can use $#path. This will use the special $path variable.
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
I have this interpreter, which prints the ARGS variable:
#!/bin/bash
echo "[$ARGS]"
I use this interpreter in another script:
#!/usr/bin/env ARGS=first interpreter
Calling the second script, I get
[first]
How do I get
[first second]
?
The short of it: don't rely on being able to pass multiple arguments as part of a shebang line, and the one argument you can use must be an unquoted, single word.
For more background information, see the question #tholu has already linked to in a comment (https://stackoverflow.com/a/4304187/45375).
Thus, I suggest you rewrite your other script to use bash as well:
#!/bin/bash
ARGS='first second' /usr/bin/env interpreter "$#"
This allows you to use bash's own mechanism for defining environment variables ad-hoc (for the command invoked and its children) by prefixing commands with variable assignments, allowing you to use quoting and even define multiple variables.
Whatever command-line arguments were passed in are passed through to interpreter via "$#".