Reputation: 2962
I started using set -e in my bash scripts, and discovered that short form of conditional expression breaks the script execution.
For example the following line should check that $var is not empty:
[ -z "$var" ] && die "result is empty"
But causes silent exit from script when $var has non-zero length.
I used this form of conditional expression in many places...
What should I do to make it run correctly? Rewrite everything with "if" construction (which would be ugly)? Or abandon "set -e"?
Edit: Everybody is asking for the code. Here is full [non]working example:
#!/bin/bash
set -e
function check_me()
{
ws="smth"
[ -z "$ws" ] && echo " fail" && exit 1
}
echo "checking wrong thing"
check_me
echo "check finished"
I'd expect it to print both echoes before and after function call. But it silently fails in the check_me function. Output is:
checking wrong thing
Upvotes: 3
Views: 3678
Reputation: 8946
What the bash help isn't very clear on is that only the last statement in an &&
or ||
chain is subject to causing an exit under set -e
. foo && bar
will exit if bar
returns false, but not if foo
returns false.
So your script should work... but it doesn't. Why?
It's not because of the failed -z
test. It's because that failure makes the function return a non-zero status:
#!/bin/bash
set -e
function check_me()
{
ws="smth"
[ -z "$ws" ] && echo " fail" && exit 1
# The line above fails, setting $? to 1
# The function now returns, returning 1!
}
echo "checking wrong thing"
check_me # function returns 1, causing exit here
echo "check finished"
So there are multiple ways to fix this. You could add ||true
to the conditional inside the function, or to the line that calls check_me
. But as others have pointed out, using ||true
has its own problems.
In this specific scenario, where the desired postcondition of check_me
is "either this thing is valid or the script has exited", the straightforward thing to do is to write it like that, i.e. [[ -n "$ws" ]] || die "whatever"
.
But using &&
conditions will actually work fine with set -e
in general, as long as you don't use such a conditional as the last thing in a function. You need to add an explicit true
or return 0
or even :
as a statement following such a conditional, unless you intend the function to return false when the condition fails.
Upvotes: 1
Reputation: 1013
I'm afraid you will have to rewrite everything so no false statements occur.
The definition of set -e
is clear:
-e Exit immediately if a simple command (see SHELL GRAMMAR above) exits with a non-zero status. 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 a && or || list, or if the command's return value is being inverted via !. A trap on ERR, if set, is executed before the shell exits.
You are using the "optimization" system of Bash: because a false statement will cause an AND (&&
) statement never to be true, bash knows it doesn't have to execute the second part of the line. However, this is a clever "abuse" of the system, not intended behaviour and therefore incompatible with set -e
. You will have to rewrite everything so it is using proper if
s.
Upvotes: 6
Reputation: 58828
Use
[ -n "$var" ] || die "result is empty"
This way, the return value of the entire statement is true if $var
is non-empty, so the ERR
trap is not triggered.
Upvotes: 9
Reputation: 25579
You should write your script such that no command ever exits with non-zero status.
In your command [ -z "$var" ]
can be true, in which case you call die
, or false in which case -e
does it's thing.
Either write it with if
, as you say, or use something like this:
[ -z "$var" ] && die "result is empty" || true
I'd recommend if
though.
Upvotes: 2