Quantcast
Channel: Ludovico – DBA survival BLOG
Viewing all articles
Browse latest Browse all 119

Bash tips & tricks [ep. 7]: Cleanup on EXIT with a trap

$
0
0

This is the seventh epidose of a small series.

Description:

Pipes, temporary files, lock files, processes spawned in background, rows inserted in a status table that need to be updated… Everything need to be cleaned up if the script exits, even when the exit condition is not triggered inside the script.

BAD:

The worst practice is, of course, to forget to cleanup the tempfiles, leaving my output and temporary directories full of files *.tmp, *.pipe, *.lck, etc. I will not show the code because the list of bad practices is quite long…

Better than forgiving to cleanup, but still very bad, is to cleanup everything just before triggering the exit command (in the following example, F_check_exit is a function that exits the script if the first argument is non-zero, as defined it in the previous episode):

...
some_command_that_must_succeed
EXITCODE=$?
if [ $EXITCODE -ne 0 ] ; then
    # Need to exit here, but F_check_exit function does not cleanup correctly
    [[ $TEMPFILE ]] && [[ -f $TEMPFILE ]] && rm $TMPFILE
    [[ $EXP_PIPE ]] && [[ -f $EXP_PIPE ]] && rm $EXP_PIPE
    if [ $CHILD_PID ] ; then
        ps --pid $CHILD_PID >/dev/null
        if [ $? -eq 0 ] ; then
            kill $CHILD_PID # or wait, or what?
        fi
    fi
    F_check_exit $EXITCODE "Some command that must succeed"
fi

A better approach, would be to put all the cleanup tasks in a Cleanup()  function and then call this function instead of duplicating all the code everywhere:

...
some_command_that_must_succeed
EXITCODE=$?
[[ $EXITCODE -eq 0 ]] || Cleanup
F_check_exit $EXITCODE "Some command that must succeed"

But still, I need to make sure that I insert this piece of code everywhere. Not optimal yet.

I may include the Cleanup function inside the F_check_exit function, but then I have two inconvenients:
1 – I need to define the Cleanup function in every script that includes my include file
2 – still there will be exit conditions that are not trapped

GOOD:

The good approach would be to trap the EXIT signal with the Cleanup function:

Cleanup() {
  # cleanup your stuff here
}

trap Cleanup EXIT

do_something
F_check_exit $? "Something"

Much better! But what if my include script has some logic that also creates some temporary files?

I can create a global F_Cleanup function that eventually executes the local Cleanup function, if defined. Let me show this:

Include script:

# this is the include file (e.g. $BASEBIN/Init_Env.sh)
function F_cleanup() {
        EXITCODE=$?
        if [ `typeset -F Cleanup` ] ; then
                edebug "Cleanup function defined. Executing it..."
                Cleanup $EXITCODE
                edebug "Cleanup function executed with return code $?"
        else
                edebug "No cleanup function defined."
        fi
        # do other global cleanups
}

### Register the cleanup function
trap F_cleanup EXIT

Main script:

# Cleanup: If any function named Cleanup is defined, it will automatically be executed
# upon the EXIT signal.
Cleanup () {
    if [ $1 -eq 0 ] ; then
        # exit 0 trapped
    else
        # exit !0 trapped
        # report the error
    fi
    # remove pipes, temporary files etc
}

. $BASEBIN/Init_Env.sh

do_something
F_check_exit $? "Something"

The Cleanup function will be executed only if defined.

No Cleanup function: no worries, but still the F_Cleanup function can do some global cleanup not specific to the main script.


Viewing all articles
Browse latest Browse all 119

Trending Articles