My Makefile includes follow target
run: $(BIN_FILE)
if [ -d $(BIN_FILE) ]; then $(error Sorry BIN_FILE is directory); else ./$(BIN_FILE) $(RUN_ARGS); fi
But it raises the error no matter whether test passes or not. What is wrong? Why it raises an error even BIN_FILE is not directory? Have directive $(error... any special meaning?
Many thanks.
You can't embed a Make expression like that inside your shell script. Make performs all expansions of $(...) expressions before the shell even starts, so it sees your $(error ...) command and exits. You would need to emit this error using shell logic instead, doing something like:
run: $(BIN_FILE)
if [ -d $(BIN_FILE) ]; then \
echo "Sorry BIN_FILE is directory"; \
exit 1; \
fi
$(BIN_FILE) $(RUN_ARGS)
Or with slightly more compact logic:
run: $(BIN_FILE)
[ -d "$(BIN_FILE)" ] && { echo "$(BIN_FILE) is a directory"; exit 1; } ||:
$(BIN_FILE) $(RUN_ARGS)
Related
This question already has answers here:
Why should there be spaces around '[' and ']' in Bash?
(5 answers)
Closed 7 months ago.
I need to add shell if-else statement to my Makefile, but the if expression always evaluates to false.
For example the next code:
if [1 -eq 1]; then echo "yes"; else echo "no"
prints "no"
The only code that evaluated to true was:
if true; then echo "yes"; else echo "no"
Why all expressions in the code (except for "true") evaluates to false? :(
I would really appreciate any help
** Please note - the statements work correctly when run from Shell
The code snippet from the original Makefile:
SIMULATION_RUN_CMD = rm -rf $(TEST_DIR)/* && mkdir -p $(TEST_DIR) && cd $(TEST_DIR) && (cp -rf $(VIVADO_PROJ)/$(PROJECT)/export_for_sim/$(SIMULATOR)/{*.mem,.mif,design.dat,nocattrs.dat,cpm_data_sim.cdo} $(TEST_DIR) || true) && \
ln -sf $(TEST_DIR)/simulation.log $(RUN_DIR)/simulation.log && \
(timeout $(SIM_TIMEOUT) ${SIM_DIR}/simv +UVM_TESTNAME=$(UVM_TESTNAME) $(SIM_FLAGS) -l $(TEST_DIR)/simulation.log -do $(DO_FILE) ; \
if [1 -eq 1]; then echo "if statement yes " >> $(TEST_DIR)/simulation.log; else echo "if statement no " >> $(TEST_DIR)/simulation.log; fi \
|| true) && \
$(MODEL_POST_SIM_ACIONS)
$(SIMULATION_RUN_TAR):
#echo -e "Make Command: $(SIMULATION_RUN_CMD)" $(PRINT_OUTPUT)
($(SIMULATION_RUN_CMD)) $(PRINT_OUTPUT)
First, you have a syntax error in your command. If you type that exactly into bash you'll get an error:
[[1: command not found
You need spaces after the [[ and before the ]] tokens:
if [[ 1 -eq 1 ]]; then echo "yes"; else echo "no"
Second, the reason it doesn't work when run from make is that make doesn't invoke bash. Make invokes the POSIX standard shell /bin/sh. If you do this you'll see the same behavior you get with make:
$ /bin/sh -c 'if [[ 1 -eq 1 ]]; then echo yes; else echo no; fi'
/bin/sh: 1: [[: not found
no
The [[ operator is a bash-specific feature. If you want to write this using POSIX features you should use:
$ /bin/sh -c 'if [ 1 -eq 1 ]; then echo yes; else echo no; fi'
yes
If you really want make to invoke bash as its shell instead of sh, add this to your makefile:
SHELL := /bin/bash
Of course then your makefile will not work on any system that doesn't have /bin/bash available.
ETA
After seeing the very much more complicated, but still not complete, code you added, I will say the following:
As I said above, you have an error in your script. if [1 -eq 1] is completely illegal. You must have spaces after [ and before ]. Again, if you run this yourself at the shell prompt you will get the same failure. It has nothing to do with make.
Because of this error, the if-statement will ALWAYS fail and so this will ALWAYS run the "else" command and print "no".
You say you don't see any error message. I can't explain that, except that you run this recipe this way:
($(SIMULATION_RUN_CMD)) $(PRINT_OUTPUT)
You don't tell use what the value of the PRINT_OUTPUT variable is, so I can only assume that it throws away stderr into the bit-bucket (or possibly, both stdout and stderr). If you didn't do that, so you could see the output, you'd see the error message being printed. Or maybe that redirects to a log file in which case, you can look there for the message.
This snippet of a makefile shall stop the execution if the file myFile does not exist:
test:
if [ -e myFile ] ; then \
echo "Error Message"; \
fi;
If I replace the echo-statement by $(error: Error Message); \ the make file is being stopped in both cases. But I need the makefile to be stopped if the file exists.
$(error ) is interpreted by make itself, so if it is hit during reading of the file, the error is produced. If you need an error during execution of a recipe, you have to run a command that returns an error exit code. Most straight-forward would be false for that, e.g.
test:
if [ -e myFile ] ; then \
echo "Error Message"; false; \
fi;
Of course, you could check for the file using $(shell ), without a recipe:
ifeq ($(shell test -e myFile && echo yes),)
$(error Error Message)
endif
So I'm currently just trying to traverse through my current directory where I'm calling the following bash script that prints 'We found a .c file' every time one is found. I have an if statement to check for args because I will be extending the script where if no args are found it will run anyway, and one arg will tell the script the directory to look in.
The issue is, this code does not work:
if [ -z "$#" ]
then
for i in *.c; do
echo "We found a .c file"
done
fi
But then if I add the echo "Test" in, it works?
if [ -z "$#" ]
echo "Test"
then
for i in *.c; do
echo "We found a .c file"
done
fi
I'm new to bash and no clue why this is happening. Can anyone help me out?
$#, which reports the count of arguments, is NEVER an empty string - if you don't specify arguments, $# evaluates to 0, which is still a nonempty string (-z tests for empty strings).
Therefore, [ -z "$#" ] is always (logically) false.
What you're looking for - using idiomatic Bash - is:
if [[ $# -eq 0 ]]; then ... # -eq compares *numerically*
As anishsane points out in a comment, the POSIX-compliant [ $# -eq 0 ] would work here as well; generally, though - unless your express intent is to write POSIX-compliant shell code - you're better off sticking with the more predictable, more feature-rich (and marginally faster) Bash-specific constructs.
or, using arithmetic evaluation:
if (( $# == 0 )); then ...
As for why your 2nd snippet caused the if branch to be entered:
Your misplaced echo "Test" - due to being placed before the then keyword, caused the echo command to be interpreted as part of the conditional.
In other words: the conditional that was evaluated was effectively
[ -z "$#" ]; echo "Test", a list of (two) commands only whose last command's exit code determined the outcome of the conditional.
Since echo always succeeds (exit code 0)[1]
, the conditional as a whole evaluated to (logical) true, and the if branch was entered.
[1] gniourf_gniourf points out in a comment that you can make a simple echo command fail (with exit code 1), if you use input/output redirection with an invalid source/target; e.g., echo 'fail' > /dev/full.
(Note that if the redirection source/target is fundamentally invalid - an nonexistent input file or an output file that can't be created / opened (as opposed to, say, an output target that can be opened with write permission but ultimately can't be written to, such as /dev/full on Linux) - Bash never even invokes the command at hand, as it "gives up" when it encounters the invalid redirection:
{ echo here >&2; echo hi; } >/dev/full # Linux: 'here' still prints (to stderr)
{ echo here >&2; echo hi; } >'' # invalid target: commands are never invoked)
Problem
The following loop will never run:
if [ -z "$#" ]
then
for i in *.c; do
echo "We found a .c file"
done
fi
The reason is that $# is a number, 0, 1 or more. It will never be an empty string. Thus [ -z "$#" ] will always fail
This loop will always run:
if [ -z "$#" ]
echo "Test"
then
for i in *.c; do
echo "We found a .c file"
done
fi
While [ -z "$#" ] always fails the second statement echo "Test" normally returns a success exit code.
Solution
If no arguments were specified on the command line, this sets the arguments to all .c files in the current directory:
[ "$1" ] || set -- *.c
for i in "$#"; do
echo "We found a .c file: $i"
done
Thus, this allows you to specify the file names on the command line and the script runs on those. If you don't specify any, it runs on all the .c files.
I've got the following Makefile script which calls a python test suite that writes the results to a file called test_results.txt. Then I display the files and read the last line which has a status that indicates whether all the test cases have been executed or not. Based on that value I echo a statement.
target: test.py
$(PYTHON) test.py
#cat test/test_results.txt
#if [ $(shell sed -e '$$!d' test/test_results.txt) = 0 ]; then\
echo "\nAll tests passed successfully";\
else \
echo "\nNot all the tests were passed";\
fi
When I run it, I get the following error: 0/bin/sh: 1: [: =: unexpected operator
It's much simpler to make test.py have a non-zero exit status if any test fails. Then your recipe is simply
target: test.py
#if $(PYTHON) test.py; then\
echo "\nAll tests passed successfully";\
else \
echo "\nNot all the tests were passed";\
fi
#cat test/test_results.txt
So I'm trying to hack one of my makefiles to be simpler (simpler, as if, not defining a lot of rules how to transform subdirectory into .deb).
build-if-need-status-vars:
#if [ ! -f debs/1.deb ]; then \
$(eval STATUS_REBUILD=1) \
echo "component: file not found: 1"; exit;\
else \
if [ $(shell find sources/ -newer debs/1.deb 2>/dev/null | wc -l) -gt 0 ]; then \
$(eval STATUS_REBUILD=1) echo "component: newer files exists: 1"; exit;\
else \
$(eval STATUS_REBUILD=0) echo "component: no newer files: 0"; \
fi;\
fi
#echo "status $(STATUS_REBUILD)"
actual-target: build-if-need-status-vars
ifeq ($(STATUS_REBUILD), 1)
#echo first status: 1
else
#echo second status: 0
#echo different action
endif
all: actual-target
.PHONY: actual-target
Test with:
mkdir -p test/{sources,debs}; touch test/debs/1.deb; sleep 2; touch test/sources/1.src; (create makefile there and run)
Result:
component: file not found: 1
status 0
second status: 0
Regardless of what conditional block is executed, STATUS_REBUILD will always be 0 (last evaluated value), try it: touch test/debs/1.deb
So it seems that last $(eval) is always used.. How to avoid this behaviour and keep the correct assigned value (from first match in build-if-need-status-var)?
$(eval) is a make-level function. It is expanded in your recipe during recipe the recipe expansion stage.
The contents of a recipe are expanded in the second phase of makefile parsing (discussed briefly in the manual here).
I believe, but cannot say for sure (without testing), that recipes are not expanded until they are about to be run (but for the purposes here that doesn't change anything either way).
So your problem here is that all the $(eval) calls are expanded by the time make goes to run your shell script so you always see the last value in effect when the last line is run.
That all being said you don't actually need a make-level variable here. Your recipe is already only two shell executions.
You can simply include the last line in the same execution as the first (split) line and use a shell variable.
build-if-need-status-vars:
#if [ ! -f debs/1.deb ]; then \
STATUS_REBUILD=1; \
echo "component: file not found"; \
else \
if [ $(shell find sources/ -newer debs/1.deb 2>/dev/null | wc -l) -gt 0 ]; then \
STATUS_REBUILD=1; echo "component: newer files exists"; \
else \
STATUS_REBUILD=0; echo "component: no newer files"; \
fi;\
fi; \
echo "status $$STATUS_REBUILD"
Note that I needed to remove the exit pieces to make this work. If those are necessary in the real makefile (because this is a stripped down sample) then you can keep them by wrapping the if in a sub-shell and/or by rewriting the recipe.