Shell script condition checking in Makefile - bash

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

Related

Shell if statement executed from Makefile always evaluates to false [duplicate]

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.

Makefile target unexpectadly raising error

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)

how to exit from running if any one of script fails?

I am running club.sh
inside club.sh script i am running below scripts.
test1.sh
test2.sh
test3.sh
my concern is it should run one by one and if test1 fails it will not run test2.sh and if test2.sh fails it willnot run test3.sh
how can we check? can any one suggest any idea it would be very helpful.
Thanks,
Two approaches -
First, you can examine the exit code of each of your inner scripts (test1.sh, test2.sh, ...) and decide whether to continue accordingly -
$? Will return the exit code of the previous command. It will be 0 (zero) if the script exited without an error. Anything that is not 0 can be considered a failure. So you could so something like this -
./test1.sh # execute script
if [[ $? != 0 ]]; then exit; fi # check return value, exit if not 0
Alternatively, you could use the && bash operator which will only execute subsequent commands if the previous one passed -
./test1.sh && ./test2.sh && test3.sh
Only if test1.sh returns an exit code of 0 (zero) will test2.sh execute and the same goes for test3.sh.
The first approach is good if you need to do some logging or cleanup between executing your scripts, but if you are only concerned that the execution should not continue if there was a failure then the && method would be they way I recommend.
Here is a related post dealing with the meaning behind &&
The returned value of the execution of the first command/script is stored in $? so using this value you can check if your command was successfully executed.
Try this:
bash test1.sh
if [ $? -eq 0 ]; then # if script succeeded
bash test2.sh
else
echo "script failed"
fi
If you want to exit your script whenever a command fails, you just add at the beginning of your script set -e.
#!/bin/bash
set -e
echo hello
ls /root/lalala
echo world
Otherwise, you have two options.
The first one is to use &&. For instance:
echo hello && ls /some_inexistant_directory && echo world
The second one is to check the return value after each command:
#!/bin/bash
echo toto
if [ "$?" != "0" ]; then
exit 1
fi
ls /root
if [ "$?" != "0" ]; then
exit 1
fi
echo world
if [ "$?" != "0" ]; then
exit 1
fi
You just have to put the below at the begging of the script:
#!/bin/bash -e

Makefile: redefining variable in runtime using #if,eval: variable always takes value of last eval

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.

bash script - unable to evaluate variable to string correctly

Trying to debug my bash script. What's wrong with my syntax here? I'm trying to evaluate a parameter entered by the user and based on that run one of my IF-THEN statements. However, I'm getting a command not found.
Here's my script thus far:
if [[ $# != 4 ]]; then
echo "Usage: ./test.sh <ABC|XYZ> <owner> <db> <TARGETHOST>" 2>&1
exit 1
fi
case $1 in
ABC|XYZ)
filename="get-$1.sql"
;;
*)echo "Must enter ABC or XYZ"
exit 1
;;
esac
export OWNER=$2
export DB=$3
export HOST_NM=$4
export PORT=5432
export LOG="test-$1.log"
PSQL=`which psql`
if [[$1=="ABC"]]; then
RUNCLI=$("$PSQL" -h $HOST_NM -p $PORT -U $OWNER $DB -F $'\t' --no-align -f get-$1.sql | tee >> $LOG)
exit 1
else
echo "Error running report ..."
fi
if [[$1=="XYZ"]]; then
RUNCLI2=$("$PSQL" -h $HOST_NM -p $PORT -U $OWNER $DB -a -f get-$1.sql | tee >> $LOG)
exit 1
else
echo "Error running report ..."
fi
Error:
./test.sh: line 41: [[XYZ==ABC]]: command not found
Error running report ...
./test.sh: line 51: [[XYZ==XYZ]]: command not found
Error running report ...
Although the question is already answered in the comment section I want to give an answer and share some knowledge which is not obvious (at least it was not for me).
The if in bash just checks the return code of the following command, which means that instead of if [ condition ]... or if [[ condition ]]... you could also write if ./configure && make....
[ and [[ are commands or shell-built-ins, respectively, as well and not part of the if syntax. An which [ for instance returns /bin/[.
At this point it is obvious that you need spaces between the brackets and the condition since it is just just a set of parameters passed to a command.
If you have this in mind, you will never forget the spaces again.

Resources