Reputation: 5955
I'm trying to execute a simple if else statement in a Makefile:
check:
if [ -z "$(APP_NAME)" ]; then \
echo "Empty" \
else \
echo "Not empty" \
fi
When I execute make check
I get the following error:
if [ -z "" ]; then
/bin/bash: -c: line 1: syntax error: unexpected end of file
make: *** [check] Error 2
Any idea what I'm doing wrong?
I know I can use the following, but I have a lot of logic after the echos so I need to spread it out across multiple lines:
check:
[ -z "$(PATH)" ] && echo "Empty" || echo "Not empty"
Upvotes: 36
Views: 58261
Reputation: 27003
Other answers already pointed out that the problem is combination of makefile design and shell syntax. The design of Makefiles make it really cumbersome to write complex recipes. Often it is better to rethink the process and either rewrite parts of the makefile or put the complexity in a shell script.
Here is example of your recipe put in a shell script:
check:
sh check.sh "$(APP_NAME)"
and the script would look like this:
if [ -z "$1" ]; then
echo "Empty"
else
echo "Not empty"
fi
($1
is special shell variable for first argument. in this case it would be $APP_NAME
from the makefile)
advantage: you have all the power and flexibility of a shell script without any of the makefile awkwardness.
disadvantage: your makefile recipes is spread across multiple files.
alternatively you can use the conditional feature of make
Here is how you might write your conditional recipe using makefile conditional feature:
check:
ifndef APP_NAME
echo "Empty"
else
echo "Not empty"
endif
the same with comments
check: # make target
ifndef APP_NAME # makefile conditional syntax
echo "Empty" # recipe if condition true
else # makefile conditional syntax
echo "Not empty" # recipe if condition false
endif # makefile conditional syntax
Note that make evaluates this condition before executing the recipe. To be more precise: this condition is evaluated by make during "startup". before any recipes are executed. by the time recipe execution begins this will rule will effectively look like this (example if APP_NAME
is not defined):
check:
echo "Empty"
I cannot say for sure if this is equivalent to your makefile because I do not have the entire makefile. For simple cases it could be equivalent. But you need to test thoroughly.
advantage: all makefile recipes in one file.
disadvantage: headaches trying to figure out when makefile does evaluation of the conditional and what the value of the variable will be at that time.
read here for more info:
see also
Upvotes: 3
Reputation: 2342
Change your make target to this (adding semicolons):
check:
if [ -z "$(APP_NAME)" ]; then \
echo "Empty"; \
else \
echo "Not empty"; \
fi
For evaluating a statement in a shell without newlines (newlines get eaten by the backslash \
) you need to properly end it with a semicolon. You cannot use real newlines in a Makefile for conditional shell-script code (see Make-specific background)
[ -z "$(APP_NAME)" ]
,
echo "Empty"
,
echo "Not empty"
are all statements that need to be evaluated (similar to pressing enter in terminal after you typed in a command).
make spawns a new shell for each command on a line, so you cannot use true multi line shell code as you would e.g. in a script-file.
Taking it to an extreme, the following would be possible in a shell script file, because the newline acts as command-evaluation (like in a terminal hitting enter is a newline-feed that evaluates the entered command):
if
[ 0 ]
then
echo "Foo"
fi
Listing 1
If you would write this in a Makefile though, if
would be evaluated in its own shell (changing the shell-state to if) after which technically the condition [ 0 ]
would be evaluated in its own shell again, without any connection to the previous if
.
However, make will not even get past the first if
, because it expects an exit code to go on with the next statement, which it will not get from just changing the shell's state to if
.
In other words, if two commands in a make-target are completely independent of each other (no conditions what so ever), you could just perfectly fine separate them solely by a normal newline and let them execute each in its own shell.
So, in order to make make evaluate multi line conditional shell scripts correctly, you need to evaluate the whole shell script code in one line (so it all is evaluated in the same shell).
Hence, for evaluating the code in Listing 1 inside a Makefile, it needs to be translated to:
if \
[ 0 ]; \
then \
echo "Foo"; \
fi
The last command fi
does not need the backslash because that's where we don't need to keep the spawned shell open anymore.
Upvotes: 56
Reputation: 100781
This is shell syntax, not makefiles. You need to familiarize yourself with the rules surrounding using backslashes to enter long commands into a single line of shell.
In your example, after the backslash newline pairs are removed, it looks like this:
if [ -z "$(APP_NAME)" ]; then echo "Empty" else echo "Not empty" fi
Maybe you can now see that the issue is. The shell interprets that as:
if [ -z "$(APP_NAME)" ]; then
followed by a single long command:
echo "Empty" else echo "Not empty" fi
which would echo the content Empty else echo not empty fi
, except that since there's no trailing fi
shell token it's instead a syntax error.
In shell syntax you need to add a semicolon after every individual command, so the shell knows how to split it up:
check:
if [ -z "$(APP_NAME)" ]; then \
echo "Empty"; \
else \
echo "Not empty"; \
fi
Note the semicolons after the echo
commands telling the shell that the command arguments end there.
Upvotes: 2