Skip to content

🇫🇷 Version Francaise de cet article

Error Handling in Bash

In Bash, it's common to see scripts with little or no error handling. There are many different approaches, but none of them are truly perfect.

One method is to create a check_error function, called after each command, to verify that the return code is equal to 0, similar to how error handling is done in Golang. However, this approach can cause issues because even simple warnings may block the script’s execution.

Another technique is to use double pipes (||) to handle errors in certain cases. But personally, I find that this greatly reduces the readability of the script. For example, you end up with something like this after every command:

bash_command || echo "Error occurred" ...
Some people go further by implementing code to check for expected results, a bit like a Test-Driven Development (TDD) approach. To me, this is somewhat mythical, like unicorns—everyone knows what they are, but no one has ever actually seen one.

After testing several methods, my current preference is to create a handle_error function, triggered using the trap keyword. This allows a function to be executed in case of an error (or on exit, which is useful for cleaning up the working directory, for example).

The key to this technique lies in the following line:

trap 'handle_error ${LINENO} "${BASH_LINENO[*]}" "${BASH_SOURCE[*]}" "${BASH_COMMAND}" "${FUNCNAME[*]:-empty}"' ERR
However, this approach doesn't necessarily stop execution in some cases of nested commands. To address this, I also use:
set -e

Unfortunately, this alone isn't enough. I’m a fan of nested functions, and without additional precautions, errors occurring in a subfunction (subfunction1), called by another function (function1), won't be caught.

That's where the following command comes in:

set -o errtrace
This ensures that the trap command catches errors even if they occur in functions called within other functions.

My template is certainly not perfect, and I welcome any feedback. In the meantime, feel free to draw inspiration from it to build a Bash script that handles errors more robustly.

Example

#!/bin/bash

set -e
set -o errtrace
trap 'handle_error ${LINENO} "${BASH_LINENO[*]}" "${BASH_SOURCE[*]}" "${BASH_COMMAND}" "${FUNCNAME[*]:-empty}"' ERR

function handle_error() {
    local last_exit_code=$?
    local line_number=$1
    local bash_lineno=$2
    local bash_source=$3
    local bash_command=$4
    local func_name=${5:-empty}

    if [ $last_exit_code -ne 0 ]; then
        echo "---------------------------"
        echo "An error occurred. Exiting."
        echo "- Function: $func_name"
        echo "- Line: $line_number"
        echo "- File: $bash_source"
        echo "- Command: $bash_command"
        echo "- Exit Code: $last_exit_code"

        # Log the error to a file
        echo "$(date) - Error in function $func_name at line $line_number in file $bash_source: command '$bash_command' exited with status $last_exit_code" >> error.log

        exit $last_exit_code

    fi
}
function subfunction1(){
    ls -lht missingFileForCreateAError_sub_f1
}

function function1(){
    subfunction1
}



main(){
    function1
}
main $@

Commentez cet article de blog en utilisant un compte compatible Fediverse (Mastodon ou similaire).