a little madness

A man needs a little madness, or else he never dares cut the rope and be free -Nikos Kazantzakis

Zutubi

Bash Tip: Reliable Clean Up With Trap

Wow, it has been over 6 years since my last bash tip. Bash scripts are still my go-to for automating the mundane and repetitive — it might not be beautiful but bash is feature rich and installed everywhere. Although a lot of my scripts are throw-away, others make it into the permanent tool kit. For scripts in the latter category, it’s worth the extra bit of effort to make them more robust.

A key part of a robust script is detecting and sensibly reporting errors. Related to this is proper clean up of work in progress when the script encounters a fatal error. Such clean up is often overlooked, as it is difficult to get right in a naive fashion. Thankfully, bash has a built-in mechanism that makes it simple: trap.

Setting a trap instructs bash to run a command when the shell process receives a specified signal. Further, bash defines pseudo-signals ERR and EXIT that can be used to trap any error or exit of the shell. I usually pair an exit trap with a clean up function to ensure my scripts don’t leave a mess. If the script runs successfully, I reset the trap (by specifying – as the command to run) and call the clean up function directly (if required). For example:

#! /usr/bin/env bash

set -e

# Defines a working area on the file system.
SCRATCH=/tmp/$$.scratch

function cleanUp() {
    if [[ -d "$SCRATCH" ]]
    then
        rm -r "$SCRATCH"
    fi
}

trap cleanUp EXIT
mkdir "$SCRATCH"

# Actual work here, all temp files created under $SCRATCH.

# We succeeded, reset trap and clean up normally.
trap - EXIT
cleanUp
exit 0

Note that the EXIT signal pairs well with set -e: if the script exits on error your EXIT trap is executed. You may also consider applying the trap to interrupts (signal INT) and kills (signal TERM) of your script. Multiple signals can be specified in one trap statement:

trap cleanUp EXIT INT TERM

If you’re used to programming with exceptions, you can think of a trap as a “finally” clause for your whole script. It’s certainly a whole lot easier and less error-prone that trying to figure out all the ways the script might exit and adding calls to clean up manually.

—-
Want Continuous Integration without the Needless Duplication? Try pulse.

Liked this post? Share it!

5 Responses to “Bash Tip: Reliable Clean Up With Trap”

  1. October 18th, 2012 at 8:39 pm

    anders says:

    Thanks for the post on multiple signals for trap.

    On a side note, I noticed that when I highlight the script for copying, the line numbers are not included–that is fantastic! Often, when I find script snippets like this one, I end up having to edit out the line numbers because they get included in the copy/paste :-(

    So, I must ask: what do you use to generate the markup for the code inclusion, or in case of a blog editor, what do you use for posting?

  2. October 19th, 2012 at 3:14 pm

    Jason says:

    Hi Anders,

    I can’t take any credit: it’s just a WordPress plugin called “Code Snippet” that I switched to last time I tweaked my blog. It seems reasonably good.

  3. October 19th, 2012 at 10:33 pm

    anders says:

    Ok, Thanks :-)

  4. January 4th, 2013 at 8:54 am

    Joris says:

    What is the benefit of running cleanUp ‘normally’ in OK situation ? i.e. why not always as an exit trap ?

  5. February 7th, 2014 at 9:55 pm

    Sheldon Hearn says:

    The approach that you provided suffers from an insecure temporary file handling vulnerability:

    SCRATCH=/tmp/$$.scratch

    It is not hard for a local attacker to predict the value of $$, preempting your use of the path with symlinks that cause you to operate on unexpected paths with your own credentials.

    Your specific example is fine, because mkdir would fail, but it’s easy to screw up adaptation of the example.

    Always use (and recommend the use of) unpredictable filenames in world-writable temporary directories. Most distributions come with a helper that makes this easy:

    SCRATCH=$(mktemp -t -d progname.XXXXXX)

    Hope that helps.

Leave a Reply