Error handling in Bash

395,579

Solution 1

Use a trap!

tempfiles=( )
cleanup() {
  rm -f "${tempfiles[@]}"
}
trap cleanup 0

error() {
  local parent_lineno="$1"
  local message="$2"
  local code="${3:-1}"
  if [[ -n "$message" ]] ; then
    echo "Error on or near line ${parent_lineno}: ${message}; exiting with status ${code}"
  else
    echo "Error on or near line ${parent_lineno}; exiting with status ${code}"
  fi
  exit "${code}"
}
trap 'error ${LINENO}' ERR

...then, whenever you create a temporary file:

temp_foo="$(mktemp -t foobar.XXXXXX)"
tempfiles+=( "$temp_foo" )

and $temp_foo will be deleted on exit, and the current line number will be printed. (set -e will likewise give you exit-on-error behavior, though it comes with serious caveats and weakens code's predictability and portability).

You can either let the trap call error for you (in which case it uses the default exit code of 1 and no message) or call it yourself and provide explicit values; for instance:

error ${LINENO} "the foobar failed" 2

will exit with status 2, and give an explicit message.

Alternatively shopt -s extdebug and give the first lines of the trap a little modification to trap all non-zero exit codes across the board (mind set -e non-error non-zero exit codes):

error() {
  local last_exit_status="$?"
  local parent_lineno="$1"
  local message="${2:-(no message ($last_exit_status))}"
  local code="${3:-$last_exit_status}"
  # ... continue as above
}
trap 'error ${LINENO}' ERR
shopt -s extdebug

This then is also "compatible" with set -eu.

Solution 2

That's a fine solution. I just wanted to add

set -e

as a rudimentary error mechanism. It will immediately stop your script if a simple command fails. I think this should have been the default behavior: since such errors almost always signify something unexpected, it is not really 'sane' to keep executing the following commands.

Solution 3

Reading all the answers on this page inspired me a lot.

So, here's my hint:

file content: lib.trap.sh

lib_name='trap'
lib_version=20121026

stderr_log="/dev/shm/stderr.log"

#
# TO BE SOURCED ONLY ONCE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

if test "${g_libs[$lib_name]+_}"; then
    return 0
else
    if test ${#g_libs[@]} == 0; then
        declare -A g_libs
    fi
    g_libs[$lib_name]=$lib_version
fi


#
# MAIN CODE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

set -o pipefail  # trace ERR through pipes
set -o errtrace  # trace ERR through 'time command' and other functions
set -o nounset   ## set -u : exit the script if you try to use an uninitialised variable
set -o errexit   ## set -e : exit the script if any statement returns a non-true return value

exec 2>"$stderr_log"


###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: EXIT_HANDLER
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

function exit_handler ()
{
    local error_code="$?"

    test $error_code == 0 && return;

    #
    # LOCAL VARIABLES:
    # ------------------------------------------------------------------
    #    
    local i=0
    local regex=''
    local mem=''

    local error_file=''
    local error_lineno=''
    local error_message='unknown'

    local lineno=''


    #
    # PRINT THE HEADER:
    # ------------------------------------------------------------------
    #
    # Color the output if it's an interactive terminal
    test -t 1 && tput bold; tput setf 4                                 ## red bold
    echo -e "\n(!) EXIT HANDLER:\n"


    #
    # GETTING LAST ERROR OCCURRED:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    #
    # Read last file from the error log
    # ------------------------------------------------------------------
    #
    if test -f "$stderr_log"
        then
            stderr=$( tail -n 1 "$stderr_log" )
            rm "$stderr_log"
    fi

    #
    # Managing the line to extract information:
    # ------------------------------------------------------------------
    #

    if test -n "$stderr"
        then        
            # Exploding stderr on :
            mem="$IFS"
            local shrunk_stderr=$( echo "$stderr" | sed 's/\: /\:/g' )
            IFS=':'
            local stderr_parts=( $shrunk_stderr )
            IFS="$mem"

            # Storing information on the error
            error_file="${stderr_parts[0]}"
            error_lineno="${stderr_parts[1]}"
            error_message=""

            for (( i = 3; i <= ${#stderr_parts[@]}; i++ ))
                do
                    error_message="$error_message "${stderr_parts[$i-1]}": "
            done

            # Removing last ':' (colon character)
            error_message="${error_message%:*}"

            # Trim
            error_message="$( echo "$error_message" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
    fi

    #
    # GETTING BACKTRACE:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
    _backtrace=$( backtrace 2 )


    #
    # MANAGING THE OUTPUT:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    local lineno=""
    regex='^([a-z]{1,}) ([0-9]{1,})$'

    if [[ $error_lineno =~ $regex ]]

        # The error line was found on the log
        # (e.g. type 'ff' without quotes wherever)
        # --------------------------------------------------------------
        then
            local row="${BASH_REMATCH[1]}"
            lineno="${BASH_REMATCH[2]}"

            echo -e "FILE:\t\t${error_file}"
            echo -e "${row^^}:\t\t${lineno}\n"

            echo -e "ERROR CODE:\t${error_code}"             
            test -t 1 && tput setf 6                                    ## white yellow
            echo -e "ERROR MESSAGE:\n$error_message"


        else
            regex="^${error_file}\$|^${error_file}\s+|\s+${error_file}\s+|\s+${error_file}\$"
            if [[ "$_backtrace" =~ $regex ]]

                # The file was found on the log but not the error line
                # (could not reproduce this case so far)
                # ------------------------------------------------------
                then
                    echo -e "FILE:\t\t$error_file"
                    echo -e "ROW:\t\tunknown\n"

                    echo -e "ERROR CODE:\t${error_code}"
                    test -t 1 && tput setf 6                            ## white yellow
                    echo -e "ERROR MESSAGE:\n${stderr}"

                # Neither the error line nor the error file was found on the log
                # (e.g. type 'cp ffd fdf' without quotes wherever)
                # ------------------------------------------------------
                else
                    #
                    # The error file is the first on backtrace list:

                    # Exploding backtrace on newlines
                    mem=$IFS
                    IFS='
                    '
                    #
                    # Substring: I keep only the carriage return
                    # (others needed only for tabbing purpose)
                    IFS=${IFS:0:1}
                    local lines=( $_backtrace )

                    IFS=$mem

                    error_file=""

                    if test -n "${lines[1]}"
                        then
                            array=( ${lines[1]} )

                            for (( i=2; i<${#array[@]}; i++ ))
                                do
                                    error_file="$error_file ${array[$i]}"
                            done

                            # Trim
                            error_file="$( echo "$error_file" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
                    fi

                    echo -e "FILE:\t\t$error_file"
                    echo -e "ROW:\t\tunknown\n"

                    echo -e "ERROR CODE:\t${error_code}"
                    test -t 1 && tput setf 6                            ## white yellow
                    if test -n "${stderr}"
                        then
                            echo -e "ERROR MESSAGE:\n${stderr}"
                        else
                            echo -e "ERROR MESSAGE:\n${error_message}"
                    fi
            fi
    fi

    #
    # PRINTING THE BACKTRACE:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    test -t 1 && tput setf 7                                            ## white bold
    echo -e "\n$_backtrace\n"

    #
    # EXITING:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    test -t 1 && tput setf 4                                            ## red bold
    echo "Exiting!"

    test -t 1 && tput sgr0 # Reset terminal

    exit "$error_code"
}
trap exit_handler EXIT                                                  # ! ! ! TRAP EXIT ! ! !
trap exit ERR                                                           # ! ! ! TRAP ERR ! ! !


###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: BACKTRACE
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

function backtrace
{
    local _start_from_=0

    local params=( "$@" )
    if (( "${#params[@]}" >= "1" ))
        then
            _start_from_="$1"
    fi

    local i=0
    local first=false
    while caller $i > /dev/null
    do
        if test -n "$_start_from_" && (( "$i" + 1   >= "$_start_from_" ))
            then
                if test "$first" == false
                    then
                        echo "BACKTRACE IS:"
                        first=true
                fi
                caller $i
        fi
        let "i=i+1"
    done
}

return 0



Example of usage:
file content: trap-test.sh

#!/bin/bash

source 'lib.trap.sh'

echo "doing something wrong now .."
echo "$foo"

exit 0


Running:

bash trap-test.sh

Output:

doing something wrong now ..

(!) EXIT HANDLER:

FILE:       trap-test.sh
LINE:       6

ERROR CODE: 1
ERROR MESSAGE:
foo:   unassigned variable

BACKTRACE IS:
1 main trap-test.sh

Exiting!


As you can see from the screenshot below, the output is colored and the error message comes in the used language.

enter image description here

Solution 4

An equivalent alternative to "set -e" is

set -o errexit

It makes the meaning of the flag somewhat clearer than just "-e".

Random addition: to temporarily disable the flag, and return to the default (of continuing execution regardless of exit codes), just use

set +e
echo "commands run here returning non-zero exit codes will not cause the entire script to fail"
echo "false returns 1 as an exit code"
false
set -e

This precludes proper error handling mentioned in other responses, but is quick & effective (just like bash).

Solution 5

Inspired by the ideas presented here, I have developed a readable and convenient way to handle errors in bash scripts in my bash boilerplate project.

By simply sourcing the library, you get the following out of the box (i.e. it will halt execution on any error, as if using set -e thanks to a trap on ERR and some bash-fu):

bash-oo-framework error handling

There are some extra features that help handle errors, such as try and catch, or the throw keyword, that allows you to break execution at a point to see the backtrace. Plus, if the terminal supports it, it spits out powerline emojis, colors parts of the output for great readability, and underlines the method that caused the exception in the context of the line of code.

The downside is - it's not portable - the code works in bash, probably >= 4 only (but I'd imagine it could be ported with some effort to bash 3).

The code is separated into multiple files for better handling, but I was inspired by the backtrace idea from the answer above by Luca Borrione.

To read more or take a look at the source, see GitHub:

https://github.com/niieani/bash-oo-framework#error-handling-with-exceptions-and-throw

Share:
395,579

Related videos on Youtube

codeforester
Author by

codeforester

Scripting and automation enthusiast. Fun fact: Code is the reason for bugs. More code = more bugs! Write reusable code!

Updated on August 20, 2021

Comments

  • codeforester
    codeforester over 2 years

    What is your favorite method to handle errors in Bash? The best example of handling errors I have found on the web was written by William Shotts, Jr at http://www.linuxcommand.org.

    He suggests using the following function for error handling in Bash:

    #!/bin/bash
    
    # A slicker error handling routine
    
    # I put a variable in my scripts named PROGNAME which
    # holds the name of the program being run.  You can get this
    # value from the first item on the command line ($0).
    
    # Reference: This was copied from <http://www.linuxcommand.org/wss0150.php>
    
    PROGNAME=$(basename $0)
    
    function error_exit
    {
    
    #   ----------------------------------------------------------------
    #   Function for exit due to fatal program error
    #       Accepts 1 argument:
    #           string containing descriptive error message
    #   ---------------------------------------------------------------- 
    
        echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
        exit 1
    }
    
    # Example call of the error_exit function.  Note the inclusion
    # of the LINENO environment variable.  It contains the current
    # line number.
    
    echo "Example of error with line number and message"
    error_exit "$LINENO: An error has occurred."
    

    Do you have a better error handling routine that you use in Bash scripts?

  • Charles Duffy
    Charles Duffy almost 13 years
    @draemon the variable capitalization is intentional. All-caps is conventional only for shell builtins and environment variables -- using lowercase for everything else prevents namespace conflicts. See also stackoverflow.com/questions/673055/…
  • Draemon
    Draemon almost 13 years
    before you break it again, test your change. Conventions are a good thing, but they're secondary to functioning code.
  • Charles Duffy
    Charles Duffy almost 12 years
    set -e is not without gotchas: See mywiki.wooledge.org/BashFAQ/105 for several.
  • hobs
    hobs over 11 years
    @CharlesDuffy, some of the gotchas can be overcome with set -o pipefail
  • Bruno De Fraine
    Bruno De Fraine over 11 years
    @CharlesDuffy Thanks for pointing to the gotchas; overall though, I still think set -e has a high benefit-cost ratio.
  • Charles Duffy
    Charles Duffy over 11 years
    @BrunoDeFraine I use set -e myself, but a number of the other regulars in irc.freenode.org#bash advise (in quite strong terms) against it. At a minimum, the gotchas in question should be well-understood.
  • Charles Duffy
    Charles Duffy about 11 years
    using $(foo) on a bare line rather than just foo is usually the Wrong Thing. Why promote it by giving it as an example?
  • Charles Duffy
    Charles Duffy about 11 years
    The function keyword is gratuitously POSIX-incompatible. Consider making your declaration just error() {, with no function before it.
  • Charles Duffy
    Charles Duffy about 11 years
    Consider using the POSIX syntax for defining functions -- no function keyword, just error_exit() {.
  • Charles Duffy
    Charles Duffy about 11 years
    ${$?} should just be $?, or ${?} if you insist on using unnecessary braces; the inner $ is wrong.
  • Dominik Dorn
    Dominik Dorn over 10 years
    this thing is awesome.. you should create a github project for it, so people can easily make improvements and contribute back. I combined it with log4bash and together it creates a powerful env for creating good bash scripts.
  • Charles Duffy
    Charles Duffy about 10 years
    FYI -- test ${#g_libs[@]} == 0 isn't POSIX-compliant (POSIX test supports = for string comparisons or -eq for numeric comparisons, but not ==, not to mention the lack of arrays in POSIX), and if you're not trying to be POSIX compliant, why in the world are you using test at all rather than a math context? (( ${#g_libs[@]} == 0 )) is, after all, easier to read.
  • Charles Duffy
    Charles Duffy about 10 years
    The function keyword is also non-POSIX; the compatible way to declare functions is foo() { ... }, with no function keyword at all.
  • Charles Duffy
    Charles Duffy almost 10 years
    @Draemon, I actually disagree. Obviously-broken code gets noticed and fixed. Bad-practices but mostly-working code lives forever (and gets propagated).
  • Sam Watkins
    Sam Watkins almost 10 years
    set -e -o pipefail -u # and know what you are doing
  • Draemon
    Draemon almost 10 years
    but you didn't notice. Broken code get noticed because functioning code is the primary concern.
  • Pierre-Olivier Vares
    Pierre-Olivier Vares almost 10 years
    is there a reason why you don't just do cd /home/myuser/afolder || error_exit "Unable to switch to folder" ?
  • Charles Duffy
    Charles Duffy over 9 years
    @Draemon, the function keyword is bad practice, introducing gratuitous incompatibility with POSIX sh for no benefit whatsoever (as opposed to this code's other incompatibilities with POSIX sh, which add value). I'd appreciate it, at this point, if you'd let my code be.
  • Draemon
    Draemon over 9 years
    it's not exactly gratuitous (stackoverflow.com/a/10927223/26334) and if the code is already incompatible with POSIX removing the function keyword doesn't make it any more able to run under POSIX sh, but my main point was that you've (IMO) devalued the answer by weakening the recommendation to use set -e. Stackoverflow isn't about "your" code, it's about having the best answers.
  • Charles Duffy
    Charles Duffy over 9 years
    @Draemon, to be sure. That said, it is accepted protocol that edits can be rejected for substantially changing the meaning -- and also that edit wars are considered exceedingly poor form. If I need provide meta links on either of those points, let me know.
  • Charles Duffy
    Charles Duffy over 9 years
    @Draemon, ...moreover, as that particular topic is one that I rather frequently and publicly rant about (as a proponent of being in the habit of writing POSIX-compliant code by default, and making deviations by intent), making a change contrary to that (1) puts my name next to practices which I'm very publicly opposed to, and (2) feels a bit like it might be deliberate trolling.
  • Draemon
    Draemon over 9 years
    you'll note that I haven't changed anything back again precisely because this has clearly descended into an edit war (which was not the intention). I wouldn't know what you're generally opposed to, but note that you included the function keyword originally. Not trolling, just have a different opinion.
  • Hieu
    Hieu over 9 years
    More explanation on set -euo pipefail can be found here: gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
  • Charles Duffy
    Charles Duffy over 9 years
    For those reading this today: Some recent versions of bash have a bug impacting accuracy of LINENO within trap handlers. Thus, there are cases where this won't work today where it used to be functional in the past.
  • niieani
    niieani about 9 years
    @Luca - this is truly great! Your picture inspired me to create my own implementation of this, which takes it even a few steps further. I've posted it in my answer below.
  • piotrekkr
    piotrekkr almost 9 years
    @CharlesDuffy Correct me if I'm wrong but I think you are needlessly using local code="${3:-1}". It's the same as local code="$3". If you want to get last character of third argument you should use local code="${3: -1}" or local code="${3:(-1)}".
  • Charles Duffy
    Charles Duffy almost 9 years
    @piotrekkr, the usage is correct as-given -- and this is not a substring operation (as it would be if I changed it per your suggestions); check behavior when $3 is and is not unset. The intended expansion -- which happens to be what this correctly functions as -- is ${varname:-defaultvalue}, not ${varname:offset}.
  • piotrekkr
    piotrekkr almost 9 years
    @CharlesDuffy you are right. It seems that I didn't understood example at tldp.org stringZ=abcABC123ABCabc; echo ${stringZ:-4} Defaults to full string, as in ${parameter:-default}. Bash syntax is quite misleading, there is only one space difference between ${3: -1} and ${3:-1} and I get two different things...
  • Charles Duffy
    Charles Duffy almost 9 years
    @piotrekkr, I'd strongly recommend using the bash-hackers reference (wiki.bash-hackers.org/syntax/pe) or the Wooledge one (see mywiki.wooledge.org/BashFAQ/073 and links from same); the ABS is rather well-known for its poor maintenance (and tendency to showcase bad practices in examples).
  • Charles Duffy
    Charles Duffy almost 9 years
    @piotrekkr, ...re: "bash syntax", ${foo:-bar} is mandatory to be compliant with POSIX sh; the only place where there would be room for bash to do anything different would be the substring syntax, and variance there would be incompatible with ksh. Thus, for compatibility's sake, there's very little choice.
  • blong
    blong almost 9 years
    Has anyone looked at creating or using a GitHub project to host a solution? This answer looks compelling. I'm considering trying it, but would really like input from any of you.
  • Charles Duffy
    Charles Duffy almost 9 years
    @blong, I did a quick code read and filed a few tickets about obvious issues some time back. Even with the more serious of those tickets resolved, however, I'm conservative enough to be wary of anything so heavy-handed (and that library is indeed heavy-handed), particularly in a language like bash with so little care about scope.
  • Charles Duffy
    Charles Duffy almost 9 years
    @blong, ...there are also several forward-compatibility issues, such as function names using characters not guaranteed by either the POSIX spec or bash documentation to be supported. Works now, sure, but that doesn't mean it'll continue to work in the future.
  • Charles Duffy
    Charles Duffy almost 9 years
    (also, grumble about using echo -e rather than printf with a format string -- means that escape sequences inside of data can be honored rather than treated literally).
  • Charles Duffy
    Charles Duffy almost 9 years
    ...also, grumble about sloppy quoting practices.
  • blong
    blong almost 9 years
    @CharlesDuffy Thanks for the feedback. In that case, can you better explain your answer? Is the usage identified by your last 2 lines? fn_facade="$(<some function>)" and then past_fns+=( "$fn_facade" ) ?
  • Charles Duffy
    Charles Duffy almost 9 years
    @blong, no -- I didn't go so generic as to create a stack of cleanup functions, though it'd be easy to do. cleanup_fns=( ) at the top; for cleanup_fn in "${cleanup_fns[@]}"; do "$cleanup_fn"; done in the handler; then, cleanup_foo() { some function; } to define a new function and cleanup_fns+=( cleanup_foo ). Though, really, one could just iterate over all defined functions named starting with cleanup_ if one wanted such a thing. The code you quoted is specific to cleanup of temporary files.
  • blong
    blong almost 9 years
    Why is it this should only be sourced once? Will something break if it's sourced multiple times?
  • blong
    blong almost 9 years
    Can you expand on the statement "We have to explicitly allow aliases" ? I'd be worried that some unexpected behavior might result. Is there a way to achieve the same thing with a smaller impact?
  • Croad Langshan
    Croad Langshan over 8 years
    @CharlesDuffy by now, POSIX is gratuitously GNU/Linux-incompatible (still, I take your point)
  • SaxDaddy
    SaxDaddy over 7 years
    Bravissimo!! This is an excellent way to debug a script. Grazie mille The only thing I added was a check for OS X like this: case "$(uname)" in Darwin ) stderr_log="${TMPDIR}stderr.log";; Linux ) stderr_log="/dev/shm/stderr.log";; * ) stderr_log="/dev/shm/stderr.log" ;; esac
  • Nelson Rodriguez
    Nelson Rodriguez over 7 years
    @Pierre-OlivierVares No particular reason about not using ||. This was just an excerpt of an existing code and I just added the "error handling" lines after each concerning line. Some are very long and it was just cleaner to have it on a separate (immediate) line
  • Tom Hale
    Tom Hale about 7 years
    set -euo pipefail; shopt -s failglob # safe mode
  • johndpope
    johndpope about 7 years
    Add a link to gist so we can all fork / improve.
  • Charles Duffy
    Charles Duffy almost 7 years
    @Draemon, set -e is actively harmful to portability (not only across shells, but also across individual releases within a shell), predictability, and consequently readability of nontrivial code. I'm entirely comfortable defending any decision against advising its use. See in-ulm.de/~mascheck/various/set-e, focused specifically on portability-related aspects of behavior.
  • iruvar
    iruvar over 6 years
    @CharlesDuffy, in the case where trap calls error would you consider using ${BASH_COMMAND} to retrieve and report the problematic command? Additionally, $? when retrieved first thing within error appears to be reporting the return code associated with the failed command - do you see any issues with saving it off and propagating it out of error?
  • Charles Duffy
    Charles Duffy over 6 years
    @iruvar, you'd need to pass it as an argument in the trap itself -- trap 'error "$LINENO" "$BASH_COMMAND" "$?"' ERR. Whereas $? will survive into the call, BASH_COMMAND won't.
  • iruvar
    iruvar over 6 years
    @CharlesDuffy, thank you. Incidentally "${BASH_COMMAND}" survives into the call to error at least in my use case. But I agree it would make sense to pass it as an argument to trap
  • Charles Duffy
    Charles Duffy over 6 years
    @iruvar, there are definitely releases of bash where it doesn't, and you get the text of the function call itself in BASH_COMMAND. I'd need to research to find the details, but from a compatibility perspective, better to to do the safe thing. (Aside: There's absolutely no value provided by "${foo}" over "$foo" except for consistency with cases where the brackets are necessary, either for a parameterized expansion or concatenation with a following string containing characters legal in names).
  • Charles Duffy
    Charles Duffy over 6 years
    @TomHale, can you answer every exercise in BashFAQ#105 (re: set -e corner cases) correctly? Are you confident you'll never hit any of the portability bugs in in-ulm.de/~mascheck/various/set-e? There's nothing "safe" about what you're calling "safe mode".
  • Buoy
    Buoy about 6 years
    @CharlesDuffy I added your trap model to a script on Bash 4.4. It does well traping errors I make at the prompt but I have not been able to trap the same error in a function. Does trap not run in functions?
  • Buoy
    Buoy about 6 years
    @CharlesDuffy I made progress with shopt -s extdebug obtained here in the 2nd answer, trap works in functions: unix.stackexchange.com/questions/419017/…
  • kyb
    kyb about 6 years
    I dont need $LINENO - 1. Show correctly without it.
  • kyb
    kyb about 6 years
    Shorter usage example in bash and zsh false || die "hello death"
  • ankostis
    ankostis about 6 years
    Better send err-message to STDERR, no?
  • Kirby
    Kirby almost 6 years
    if test ${#g_libs[@]} == 0 is about declaration, it would be better to use similar: declare -p g_libs > /dev/null 2> /dev/null || declare -A g_libs.
  • Charles Duffy
    Charles Duffy over 4 years
    @RickLan, the "the" you proposed adding implied that set -e only weakens this specific code's predictability and portability. That's not what the prose was written to state; set -e weakens all code's predictability and portability. The original grammar was thus correct for the intended meaning.
  • Alexej Magura
    Alexej Magura over 4 years
    This is not a solution, it's a band-aid. set -e can easily and should be replaced with actual (robust) error checking.
  • Someguy123
    Someguy123 over 4 years
    A bit of a shameless self-plug, but we've taken this snippet, cleaned it up, added more features, improved the output formatting, and made it more POSIX compatible (works on both Linux and OSX). It's published as part of Privex ShellCore on Github: github.com/Privex/shell-core
  • ingyhere
    ingyhere over 4 years
    Wouldn't $_ be available in the function the same as $?? I'm not sure there is any reason to use one in the function but not the other.
  • ingyhere
    ingyhere over 4 years
    This is inside the Bash Object Oriented Framework project. ... Luckily it only has 7.4k LOC (according to GLOC ). OOP -- Object-oriented pain?
  • niieani
    niieani over 4 years
    @ingyhere it's highly modular (and delete-friendly), so you can only use the exceptions part if that is what you came for ;)
  • Elie G.
    Elie G. over 4 years
    Do I need to enable the errexit shell option in order for the trap to work? @CharlesDuffy Thanks for your help! It seems like everytime I look for something related to bash yo̲u̲ have the answer and it is always the best one.
  • Charles Duffy
    Charles Duffy over 4 years
    No, you don't need to enable errexit for an ERR trap to trigger. (That said, ERR traps are subject to the same general caveats errexit is, making them... less than reliable).
  • Elie G.
    Elie G. over 4 years
    What should I use then?
  • Charles Duffy
    Charles Duffy over 4 years
    Explicit, manual error handling, same as you would in C or Go or another language that doesn't support exceptions -- check each command's return value, explicitly act accordingly.
  • mhulse
    mhulse about 4 years
    Looks like a clean solution, though, shell check complains: github.com/koalaman/shellcheck/wiki/SC2181
  • Jacktose
    Jacktose over 3 years
  • Henshal B
    Henshal B about 2 years
    Got it clean. But command is actually needed?