Search code examples
error-handlingshposix

Keep set -e setting inside || or &&


I have a simple script with a simple function which can lead to an error. Let's define this function, and make it broken:

brokenFunction () {
    ls "non-existing-folder"
}

If we execute this function in a block detecting if it is broken, it works well:

brokenFunction || printf "It is broken\n"

prints "It is broken"

Now, let's make the function a bit more complex, by adding a correct command at the end :

#!/bin/sh

brokenFunction () {
    ls "non-existing-folder"
    printf "End of function\n"
}

brokenFunction || printf "It is broken\n"

This script prints :

$ ./script.sh 
ls: cannot access 'non-existing-folder': No such file or directory
End of function

while I expected the function to stop before the printf statement, and the next block to display "It is broken".

And indeed, if I check the exit status code of brokenFunction, it is 0.

I tried adding set -e to the top of the script. The behavior is still the same, but the exit code of brokenFunction if called without || now becomes 2. If called with it, the status code is still 0.

Is there any way to keep the set -e setting inside a function called with ||?

EDIT: I just realized that the function in the example was useless. I encounter the same issue with a simple block and a condition.

#!/bin/sh
set -e
{
    ls "non-existing-dir"
    printf "End of block\n"
} || {
    printf "It is broken\n"
} 

prints

$ ./script.sh 
ls: cannot access 'non-existing-dir': No such file or directory
End of block

Solution

  • As written in man bash, set -e is ignored in some contexts. A command before || or && is such a context.

    trap looks like a possible solution here. A working alternative to the last script using trap would look like that:

    #!/bin/sh
    abort () {
        printf "It is broken\n"
    }
    trap 'abort' ERR
    (
        set -e
        false
        printf "End of block\n"
    )
    trap - ERR
    

    Some things have to be noticed here:

    • trap 'abort' ERR binds the abort function to any raised error ;
    • the broken block is executed in a sub-shell for 2 reasons. First is to keep the set -e setting inside the block and limit the border effects. Second is to exit this sub-shell on error (set -e effect), and not the whole script ;
    • trap - ERR at the end resets the trap binding, meaning the following part of the script is executed as before.

    To test the border effects, we can add the previously non-working part :

    #!/bin/sh
    abort () {
        printf "It is broken\n"
    }
    
    trap 'abort' ERR
    (
        set -e
        false
        printf "End of block\n"
    )
    trap - ERR
    
    {   
        false
        printf "End of second block\n"
    } || {
        printf "It is broken too\n"
    }
    

    prints:

    It is broken
    End of second block