Non-trivial command-line fu

trap "cleanup" 0

— -sh (@rtfmsh) August 23, 2013

Alright, so I lied -- this is actually trivial. But it's still worth mentioning, since so few people seem to be aware of the concept of an exit handler:

Programs, and shell scripts in particular, often need a place to stash some data for a short amount of time. That alone is already more error-prone than you might think -- just honoring the TMPDIR environment variable already gets you brownie points. Removing temporary files when the program terminates often isn't on the author's radar, much less so when the program terminates unexpectedly, for example as the result of a signal.

Go ahead, look around on your servers in /tmp and see how many files you can find that were created automatically -- for example by some cron job, or perhaps your configuration management system -- and that should have been removed. On some systems I've counted thousands.

To make your program behave and clean up after itself, just define a function called cleanup that nukes any and all temporary files you've created. I tend to create a unique directory for my script, usually using something like this:

_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXX")

cleanup() {
	${DONT} rm -fr "${_TMPDIR}"

trap "cleanup" 0

(I also have a habit of setting the DONT variable to echo if the program was passed a debug flag, allowing me to keep and review the files the program creates if so desired. If not in debugging mode, the variable remains unset.)

Now of course you can do other nifty things in response to receiving certain signals. One of my favorite signals (besides SIGTSTP) is SIGINFO. If your program runs for a long time, it'd be rather nice of you to allow the user to inquire about the progress it makes by implementing a signal handler for SIGINFO, which you usually can send to the running process via ^T:

NUMBER_LEFT=$(ls | wc -l)

status() {
        echo "Processed ${NUMBER_DONE} files, ${NUMBER_LEFT} left."

something() {
	sleep 1
	# normally: do something

trap "status" INFO

for f in *; do
	something ${f}

Oh, and don't bother trying to trap signals 9 (KILL) and 17 (STOP) -- you can't. You probably also do not want to trap signal 15 (TERM), 18 (TSTP), or 19 (CONT), as doing so will make your users dislike your program. Most of the time, I find it useful to let the default action take place and only cleanup upon exit via the trap "cleanup" 0 statement.


P.S.: One more thing I wish more people did is ensuring proper file permissions for any of their (temporary) files at the start of their program. It doesn't fit into this post, but I'll throw it in as a freebie: umask 077.

P.P.S.: If you like exit handlers, you might also like function-scoped exit handlers, which I think are a particularly neat idea.

[previous] [Index]