Adam
Adam

Reputation: 6733

How to make bash function return error if syntax error

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

Answers (3)

Dmitry
Dmitry

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

user803422
user803422

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

hek2mgl
hek2mgl

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

Related Questions