I have the following bash script which runs on my CI and intends to run my code on a physical MacOS and on several docker Linux images:
if [[ "$OS_NAME" == "mac_os" ]]; then
make all;
run_test1;
run_test2;
make install;
else
docker exec -i my_docker_image bash -c "make all";
docker exec -i my_docker_image bash -c "run_test1";
docker exec -i my_docker_image bash -c "run_test2";
docker exec -i my_docker_image bash -c "make install";
fi
If the tests fail (run_test1
or run_test2
) they return error code 1. If they pass they return error code 0.
The whole script runs with set -e
so whenever it sees exit code other than 0 it stops and fails the entire build.
The problem is that currently, when run_test1
and run_test2
are inside the conditional clause - when they fail and return error code 1 the conditional clause doesn't break and the build succeeds although tests didn't pass.
So I have 2 questions:
Your code should work as expected, let's demonstrate this:
#!/usr/bin/env bash
set -e
var="test"
if [[ $var = test ]]; then
echo "Hello"
cat non_existing_file &> /dev/null
echo "World"
else
echo "Else hello"
cat non_existing file &> /dev/null
fi
echo I am done
This will output only "Hello", as expected. If it works differently for you, it means you didn't provide enough code. Let's try to change the code above and show some examples when set -e
is ignored, just like in your case:
Quoting Bash Reference manual:
If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status.
Now let's quote the same manual and see some cases where -e is ignored:
The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in an if statement, part of any command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command’s return status is being inverted with !.
From this we can see that, for example, if you had the code above in a function and tested that function with if
, set -e
would be ignored:
#!/usr/bin/env bash
set -e
f() {
var="test"
if [[ $var = test ]]; then
echo "Hello"
cat non_existing_file &> /dev/null
echo "World"
else
echo "Else hello"
cat non_existing file &> /dev/null
fi
}
if f; then echo "Function OK!"; fi
echo I am done
Function f
is executed in a context where set -e
is being ignored (if statement), meaning that set -e
doesn't affect any of the commands executed within this function. This code outputs:
Hello
World
Function OK!
I am done
The same rules apply when you execute the function in a && or || list. If you change the line if f; then echo "Function OK!"; fi
to f && echo "Function OK"
, you will get the same output. I believe the latter could be your case.
Even so, your second question can be solved easily by adding || exit
:
run_test1 || exit 1;
run_test2 || exit 1;
Your first question is trivial if you are inside a function for example. Then you can simply return
. Or inside a loop, then you can break
. If you are not, breaking out of the conditional clause is not that easy. Take a look at this answer.
set -e
can be a surprising thing as it is being ignored in many cases. Use it with care and be aware of these cases.