Reputation: 6733
I made a few changes to a bash script recently, and made a syntax error in a function. I know what the error is (spaces around the assignment: local max_loops = 4
instead of local max_loops=4
).
However, I didn't spot this at first - it's a long script with a lot of output and it just output errors but continued running - the function returned true, so despite testing for success of the function I got no errors.
A simplified version is here (updated based on comments and answers below):
!/bin/bash
set -e
do_magic(){
set -e
local i=1
local max_loops
max_loops = 4 # This is the error i want the script/function to break on
while [ $i -le $max_loops ]; do
echo "Loop: $i"
i=$(( i + 1 ))
done
}
if do_magic; then
echo "Ran OK"
else
echo "Error running...."
fi
(My actual function does stuff in the loop and returns an error code if the stuff fails). This just outputs:
$ ./foo.sh
./foo.sh: line 1: !/bin/bash: No such file or directory
./foo.sh: line 8: max_loops: command not found
./foo.sh: line 9: [: 1: unary operator expected
Ran OK
My Question: Is there any generic way to say that functions with unhandled errors return a non-zero value? I tried set -e
(I know I shouldn't need it twice, but tried both for safeties sake) and it doesn't seem to help.
I'd be happy either with the whole script breaking and stopping, or with the function breaking and returning a non-zero error. But i don't want to see "Ran OK" here...
I've fixed the actual error here, but I'd like to make the script more robust.
Upvotes: 3
Views: 2118
Reputation: 803
This is not a syntax error
, but one of the most common Bash pitfalls
The local
keyword is a Bash builtin. Be careful with local
, because for historical reasons this statement returns a success status
that you might not expect (Bash manual). Here is an example:
local x=$(false) # $? == 0
vs.
local x
x=$(false) # $? == 1
But in your case, the expression
local max_loops = 4
is equivalent to
local max_loops
local = # error, $? == 1 - an invalid name is supplied
local 4 # error, $? == 1
So, you have a command with the nonzero return status. Making script exit automatically with the -errexit
shell flag doesn't help because of the execution context of do_magic
, see here.
The following will work fine with set -e
do_magic
echo "Ran OK"
Just remember that if you rely on set -e
, never change the context. Even executing your script as
if ./do_magic.sh; then
:
fi
enough for set -e
magic to disappear.
Upvotes: 0
Reputation: 2814
Use set -e
.
Example (t.sh):
set -e
x = r
echo hello
Execution:
$ bash t.sh
t.sh: line 3: x: command not found
$ echo $?
127
EDIT:
And you need to change the if do_magic
as follows:
do_magic
if [ $? -eq 0 ]; then
...
Upvotes: 0
Reputation: 157927
Basically syntax errors can't be handled by code because the syntax has to be parsed before the code can run. I think this is what you assume.
However, in this case it is not a syntax error in bash, it is a syntax error raised by the [
(or test
) command builtin.
Note that [
is an external* command, it is more or less an alias to the test
command. You can replace:
[ 1 -lt 2 ]
by:
test 1 -lt 2
for example.
This kind of error can only be detected at runtime of the bash script, and the bash script won't fail by default It is just like any other command that fails. Well, with one exception: If you use set -e
, the [
command won't fail the script.
* nowadays realized as a bash builtin. but it is not bash syntax
How to solve it?
bash
offers the [[
(extended comparison) for this (plus other advantages):
foo() {
# Oops! missing the dash in front of 'lt'
if [[ 1 lt 2 ]] ; then
echo "foo"
fi
}
echo "Don't print this"
foo
echo "And this"
Since the [[
is bash
syntax, not an external command, bash can handle this error at parsing stage, before the code runs. The result is that script doesn't run at all.
Upvotes: 3