Makefile variable in commands? - makefile

I know that GNU make provides a way to reference targets and dependencies via $^ and $#. Is there a way to reference actual command arguments? For example:
foo: bar
myscript arg1 arg2 arg3
ls arg3
I would like a way to execute ls with a variable that is the last argument from the line above. Obviously, I could define a variable MYVAR := args, but I was looking for a way to do this in make that would require manually defining the variable.

Related

How to use bash -c with arguments [duplicate]

The man page for Bash says, regarding the -c option:
-c string
If the -c option is present, then commands are read from
string. If there are arguments after
the string, they are assigned to the
positional parameters, starting with
$0.
So given that description, I would think something like this ought to work:
bash -c "echo arg 0: $0, arg 1: $1" arg1
but the output just shows the following, so it looks like the arguments after the -c string are not being assigned to the positional parameters.
arg 0: -bash, arg 1:
I am running a fairly ancient Bash (on Fedora 4):
[root#dd42 trunk]# bash --version
GNU bash, version 3.00.16(1)-release (i386-redhat-linux-gnu)
Copyright (C) 2004 Free Software Foundation, Inc.
I am really trying to execute a bit of a shell script with arguments. I thought -c looked very promising, hence the issue above. I wondered about using eval, but I don't think I can pass arguments to the stuff that follows eval. I'm open to other suggestions as well.
You need to use single quotes to prevent interpolation happening in your calling shell.
$ bash -c 'echo arg 0: $0, arg 1: $1' arg1 arg2
arg 0: arg1, arg 1: arg2
Or escape the variables in your double-quoted string. Which to use might depend on exactly what you want to put in your snippet of code.
Because '$0' and '$1' in your string is replaced with a variable #0 and #1 respectively.
Try :
bash -c "echo arg 0: \$0, arg 1: \$1" arg0 arg1
In this code $ of both are escape so base see it as a string $ and not get replaced.
The result of this command is:
arg 0: arg0, arg 1: arg1
Hope this helps.
martin is right about the interpolation: you need to use single quotes. But note that if you're trying to pass arguments to a command that is being executed within the string, you need to forward them on explicitly. For example, if you have a script foo.sh like:
#!/bin/bash
echo 0:$0
echo 1:$1
echo 2:$2
Then you should call it like this:
$ bash -c './foo.sh ${1+"$#"}' foo "bar baz"
0:./foo.sh
1:bar baz
2:
Or more generally bash -c '${0} ${1+"$#"}' <command> [argument]...
Not like this:
$ bash -c ./foo.sh foo "bar baz"
0:./foo.sh
1:
2:
Nor like this:
$ bash -c './foo.sh $#' foo "bar baz"
0:./foo.sh
1:bar
2:baz
This means you can pass in arguments to sub-processes without embedding them in the command string, and without worrying about escaping them.
Add a backslash to the $0 (i.e., \$0), otherwise your current shell escapes $0 to the name of the shell before it even gets to the subshell.

Testing the eval function of GNU make

The GNU make manual says that the eval function expands the arguments and then feeds the results of the expansion to make parser. The following is quoted from GNU make manual.
The argument to the eval function is expanded, then the results of that expansion are parsed as makefile syntax.
I don't quite understand how the make parser process the text fed by eval, so
I write following makefile to test.
define myprint
echo "this is a line"
endef
goal:
$(eval $(call myprint))
gcc -o goal test.c
I know that the correct invocation of myprint should be only use the call function: $(call myprint) and delete the 'Tab' character before echo. I write the makefile in this form just to test the eval function.
My expectation: first the eval function expands myprint, which is an echo command preceded by a 'Tab', and the 'Tab' is used to make the expanded text to be a legal recipe. Then the eval feeds the expanded text to maker parser, who will identify the text to be a recipe, and run it. As the command is legal, the makefile should run properly.
However, I meet such an error:
Makefile:6: *** recipe commences before first target. Stop.
Could somebody explain why make produce such an error?
the results of that expansion are parsed as makefile syntax
Your use of eval is different: you would like it to be parsed as shell syntax. You can write:
define myprint
echo "this is a line"
endef
goal:
$(myprint)
gcc -o goal test.c
or:
define myprint
echo "this is a $(1)"
endef
goal:
$(call myprint,line)
gcc -o goal test.c
Because after make expansion the recipes are valid shell syntax. But not what you wrote because the expansion of eval is still interpreted as make syntax, not shell. To illustrate a typical use of eval and call, consider this:
define myprint
echo "this is a $(1)"
endef
define mygoal
$(1):
$$(call myprint,line)
gcc -o $(1) $(2).c
endef
$(eval $(call mygoal,goal,test))
It is a bit more tricky than two first examples (without eval) but it illustrates the real purpose of eval: programmatically instantiate make constructs. Here is how it works, step by step:
During the first phase of its 2-phases algorithm, make expands the $(eval... function call, that is:
Expand the parameter of the $(eval... function (the $(call... function):
Expand the parameters of the $(call... function (goal and test). No effect in our case.
Assign the result to the temporary variables $(1) and $(2).
Expand the mygoal variable in this context, which replaces $(1), $(2) and $$(call... by goal, test and $(call..., respectively.
Instantiates (in memory) the result as a make construct, a complete rule in this case:
goal:
$(call myprint,line)
gcc -o goal test.c
The first phase continues but it has no effect on this instantiated rule because the recipes are expanded by make during the second phase.
During the second phase, when the time comes to build the goal target, make expands the recipe before executing it, that is:
Expand the $(call myprint... parameter (line, no effect).
Assign the result to temporary parameter $(1).
Expand variable myprint in this context, which produces:
echo "this is a line"
All this is thus the same as if we had written the rule:
goal:
echo "this is a line"
gcc -o goal test.c
Note the double $$ in the initial definition of mygoal:
It’s important to realize that the eval argument is expanded twice;
first by the eval function, then the results of that expansion are
expanded again when they are parsed as makefile syntax. This means you
may need to provide extra levels of escaping for “$” characters when
using eval.
$(eval …) needs a syntactically complete makefile fragment. It cannot be used to paste tokens into other makefile constructs. Perhaps the manual does not explain this clearly enough, but it's implemented by reading its argument as if it were an included makefile.
#RenaudPacalet, I write following makefile to test whether the expansion of call 'eats' one dollar.
define myprint
echo "In my print $$(ls)"
endef
goal:
$(call myprint)
$(info $(call myprint))
gcc -o goal test.c
Its output is:
echo "In my print $(ls)"
echo "In my print $(ls)"
In my print call.mk ... (files list)
As $(call myprint) outputs "In my print $(ls)" correctly, it must be expanded to echo "In my print $$(ls)" first, then it will be expanded to the correct shell command echo "In my print $(ls)". So I think the call function does not 'eats' one dollar.
Another evidence is the output of info function. The GNU make manual says:
$(info text…)
This function does nothing more than print its (expanded) argument(s) to standard output.
From the manual we can infer that make will expand the arguments of the info function. As the output of the info is echo "In my print $(ls)", so the arguments before expansion should be echo "In my print $$(ls)". So we can conclude that the call function doesn't 'eat' one dollar.

Set newly created file as variable in makefile

I am trying to create a file and set the contents of a file to a variable but the variable is always seen as empty.
An example:
define FILE_VAR
cat /output/1.txt >> /output/2.txt
$(eval s := $(shell cat /output/2.txt))
$(eval FILENAME_BIN=$(word 1, $(s)).$(word 2, $(s)).$(word 3, $(s)).bin)
$(eval FILENAME_JFFS2=$(word 1, $(s)).$(word 2, $(s)).$(word 3, $(s)).jffs2)
endef
If 2.txt exists before running this the variables will be set to the data prior to running make (not the new redirected data), if 2.txt doesn't exist then the variables are not set. It looks like it is evaluating what the file is like when make is first run, which is not what I want...
You are unclear as to what is done by GNU make, and when, and what is done by the shell,
and when, to execute a recipe.
Anything anywhere in a makefile of the form $(...) is evaluated by make
unless it escaped as $$(...) in a shell command.
A shell command, unless in the context of the make-function $(shell ...) or
back-ticks can only be a command within a recipe:
target: [prerequisite...]
command
...
The commands composing a recipe are executed in sequence, each in a distinct
shell, to execute the recipe.
Nothing of the unescaped form $(...) is a command in the command-sequence
of a recipe unless it is the expansion of a variable or macro you have defined that expands to a command.
A line in the scope of a target need not be a command or expand to a command. It
may also consist of an $(...) expression that expands to nothing,
and simply instructs make to do something, e.g.
$(info ...)
expands to nothing and tells make to print ... on the standard output.
$(eval ...)
expands to nothing and tells make to evaluate ...
This recipe has just two commands, not four:
target: prereq...
$(eval TARGET=$#)
command1
$(info $(TARGET))
command2
Any make-expressions, $(...) in the scope of a recipe are evaluated in
sequence when make decides to run the recipe and the command-sequence is what
is left after they have all been evaluated. Then
the command-sequence is run. For example:
Makefile
target:
echo Hello > hello.txt
$(info Begin {)
$(eval s := $(shell cat hello.txt))
$(eval WORD=$(word 1, $(s)))
$(info [$(WORD)])
$(info } End)
Run that:
$ make
Begin {
cat: hello.txt: No such file or directory
[]
} End
echo Hello > hello.txt
Observe that all of the make-expressions are evaluated before the
commands of the target are run.
Your FILE_VAR macro is evidently intended to be expanded in recipe scope, since the first line is a shell command.
The remaining lines must therefore achieve their purposes by shell commands if they depend upon the
the effects of running the first line. None of them does so. The remaining 3 lines
are evaluated before 2.txt exists, if it doesn't already exist at make-time.

Makefile subst variable not affected?

I want to perform a string substitution in my Makefile. I can easily do this with a string literal like so:
foo:
echo $(subst /,-,"hello/world")
Which yields the expected:
hello-world
But when I switch to using a variable, I can't seem to get the substitution to stick:
foo:
x="hello/world" ; \
echo $(subst /,-,$$x)
Instead of replacing the slash with a dash, I still get the original string printed back. Can someone explain what is going on here? Does the variable need to be explicitly converted to a string literal or something?
UPDATE:
The fix based on MadScientist's answer--this will allow me to reference the modified string as a variable.
foo:
x="hello/world" ; \
y=`echo $$x | tr / -` ; \
echo $$y
But instead of echo $$y this could be something more useful.
You can't combine make functions with shell variables... all make functions are expanded first, then the resulting script is passed to the shell to be run. When the shell gets the script there are no more make functions in it (and if there were, the shell wouldn't know what to do with them!)
Your subst is running on the literal string $x, which has no / so nothing to replace and results in $x, which the shell expands to the string hello/world.
If you have to work on a shell variable value you must use shell commands such as sed or tr, not make's subst function:
foo:
x="hello/world" ; \
echo $$x | tr / -
You could define x as a make variable:
Makefile:
x = foo bar baz
t:
#echo $(subst bar,qux,$(x))
Output:
make
foo qux baz
Version:
make --version
GNU Make 3.81

Is there a way to write "$#" to a new file from a Makefile?

I've been trying to create an executable file from a Makefile to run a java program and I need the executable to have the text: java program $#. This is so any arguments will be passed into the program when it runs.
From the Makefile, I've been trying to echo into the new file:
#echo "java Program $#" >> exec
However, $# results in "all" being written to exec, and $$# becomes blank when written to exec. Is there another way to escape this sequence in a Makefile? I essentially need it to ignore $#.
I appreciate your time.
$$ is the correct way to escape the $ for Make, but the result is then being expanded by Bash. Try this:
#echo 'java Program $$#' >> exec
Single quotes in Bash mean that variables don't get expanded.

Resources