DX Application Performance Management

 View Only

Shell scripting: error handler

By Jörg Mertin posted Feb 26, 2020 09:06 AM

  

Shell scripting: Error handler

Most scripts will be used by writing commands in a certain order, some if's and switch statements. But the fewest scripts actually do something if something does not execute as expected. Then - the program is stuck because the "exception" - or in other words, the return code of the executed program, is not interpreted correctly.
This is the main reason shell-scripts execute, but errors are not detected and nothing is done about.

The simplest way of catching the return code of an executed program in a shell-script is done reading the $? variable.
For example:

#!/bin/sh

# Move a file as a user to a place we don't have access to
if [ -f file.ba ]
then
   mv file.ba /foo.ba 2>/dev/null
   # Assign the return code of the last program execution
   error=$?

   # Echo the error code back to the console so we can see it.
   echo "Returned code: $error"
fi

Now - whatever happens, we'll catch the return code.
You will notice that the 2> /dev/null behind the mv command. This is to redirect all error message from the console to /dev/null which is virtually the trash-can of the Linux OS.

One can also move these to a variable and have the error message to be reported to the logger function we handled in the log handler examples. Executing this script will look like:

jmertin@calypso:~/work/2020/Blogs/Errorhandler$ ./error_handler.sh 
Returned code: 1

If we now write an error handler to take into account various return codes (15 being a Warning for example, 1 being a general error), one could come up with the following function:

###############################################################################
# errors function
#
# Required information/VARIABLES
# errlvl: return code of the last ran program
# MSG: Message to display
# 
# Call with: "errors" at the appropriate location in the code.
errors() {
    # Set default exist level
    : ${errlvl:=9}
    # Set default error message
    : ${MSG:="No Error message - Probably user interruption"}

    # 
    if [ $errlvl -gt 0 ] ;
    then
    # Level 15 is just a Warning. So issue a Warning
    if [ $errlvl = 15 ] ;
    then
            # Using the Log function from our previous examples
            log "WARNING: $MSG"
    else
            # We have an error different from 0 or 15! Exit issuing a Warning message 
            # audio notification if possible.
            echo -e "a"
        echo "========================================================================" 
            echo "*** FATAL:  An error occured in "${PROGNAME}(${FUNCTION})". Bailing out..."
            log "FATAL: $MSG"
            exit $errlvl
    fi
    fi
}

Let's add it to our library file shmod.inc we crteated in our previous blog and add the execution code example to the "sample.sh" into the actual script section:

#######################################################################
# Actual script - do not modify anything below this poin
#######################################################################

# One way of using the log-message, put it all into the current MSG
# variable and invoque internal funciton "log"
MSG="Starting program $PROGNAME"
log $MSG

if [ -f file.ba ]
then
    # Apply error message assuming the worst-case.
    MSG="Move file.ba to /foo.ba failed"
    mv file.ba /foo.ba 2>/dev/null
    # Assign the return code of the last program execution
    errlvl=$?
    errors

    # Echo the error code back to the console so we can see it.
    echo "Returned code: $error"
else
    MSG="file.ba does not exist"
    errlvl=1
    errors
fi

# Log the operation. In case an error occured, we exit anyway
log $MSG

We will now handle 3 cases:

  1. The file file.ba does not exist.
  2. The file.ba does exist, but the user has no write access to /
  3. The file.ba does exist, and th4e path to move the file has been modified to /tmp

This will result in the following runs:

jmertin@calypso:~/work/2020/Blogs$ ./sample.sh
>>> sample[24924]: Starting program sample

========================================================================
*** FATAL:  An error occured in "sample()" code 1. Bailing out...
>>> sample[24924]: FATAL: file.ba does not exist

Let's create the file file.ba and execute the script again:

jmertin@calypso:~/work/2020/Blogs$ echo "bla" > file.ba
jmertin@calypso:~/work/2020/Blogs$ ./sample.sh
>>> sample[24979]: Starting program sample

========================================================================
*** FATAL:  An error occured in "sample()" code 1. Bailing out...
>>> sample[24979]: FATAL: Move file.ba to /tmp/foo.ba failed

At last, modify the target directory in our script to point to /tmp:

jmertin@calypso:~/work/2020/Blogs$ diff -uN sample.sh_old sample.sh 
--- sample.sh_old       2020-02-03 16:35:19.017966025 +0100
+++ sample.sh   2020-02-03 16:35:29.454001894 +0100
@@ -58,7 +58,7 @@
 then
     # Apply error message assuming the worst-case.
     MSG="Move file.ba to /foo.ba failed"
-    mv file.ba /foo.ba 2>/dev/null
+    mv file.ba /tmp/foo.ba 2>/dev/null
     # Assign the return code of the last program execution
     error=$?
     errors

and execute it again:

jmertin@calypso:~/work/2020/Blogs$ echo "bla" > file.ba
jmertin@calypso:~/work/2020/Blogs$ ./sample.sh
>>> sample[21482]: Starting program sample
>>> sample[21482]: Move file.ba to /tmp/foo.ba Succeeded

If you still have the configuration set to log to syslog, this is what syslog will have recorded:

Feb  6 11:28:40 calypso sample[21482]: Starting program sample
Feb  6 11:28:40 calypso sample[21482]: Move file.ba to /tmp/foo.ba Succeeded


We have just created a small function to show how to handle the return codes of various executed programs, scripts or even internal functions. As long as the return code is 0, everything is fine. A 15 will just issue a Warning message. This is one of the codes one can use to manually issue WARNINGS to show up in the logs. But the main advantage of handling errors is that in fact, every error will make the script exit, and this indirectly will point the developer to the part of code that needs fixing. And, when nothing needs fixing anymore, the code is running as it is meant to run, taking into account all possible eventualities that would make the script execution fail.

Next week: Shell scripting: using lock-files - or how can I prevent my script to be executed more than once.

0 comments
9 views

Permalink