http://bjoernstechblog.rueffer.info/posts/bash/mail/imap/2010/11/30/Run-commands-non-concurrently/
last updated on 25 May 2018

30 November 2010

Run commands non-concurrently

Recently I found this fantastic tool that lets you pull mails from an IMAP account and push them into another one. Basically implementing a forwarding functionality for me for a certain university outlook mail server (ugly, I know) to a certain free mail service (lets call it gmail) which offers IMAP access and active “pulling” of mails from POP servers, but not from IMAP servers.

The tool I’m talking about is imapfilter. You can set it up such that cron runs it every such and such minutes, but beware that you never ever run two instances of the process at the same time. Otherwise bad things can happen, and of course you don’t want that to happen to your mail.

To ensure that only one instance runs at the same time, I put together the following little script. This allows me to have cron try and run imapfilter every minute without having to fear that I loose mail.

The corresponding crontab entry reads

*/2 * * * *	  runnonconcurrently.sh imapfilter -l/home/myname/imapfilter_error.log >/dev/null 2>&1

And here’s the the script:

#!/bin/bash
# Copyright (C) 2010 by Bjoern Rueffer

function usage (){
    # display usage information
    CMDNAME=$(basename $0)
    cat<<EOF

$CMDNAME Copyright (c) 2010 Bjoern Rueffer. Run a
command/program non-concurrently, i.e., avoid that
several instances of the same program run at the
same time.

Usage: $CMDNAME command [command parameters]

Return values: -1 if you see this help or if there
is a stale lock file which cannot be obtained; 
0 if another process is already running; otherwise
the return value of the sub process is returned.


EOF
    exit -1
}

function escapestr (){
    # escape all non alpha numerical characters
    # for file name generation
    echo -n "$1" | sed -e s:[^a-zA-Z0-9]:_:g
}

function runsubprocess(){
    # $LOCKFILE has been successfully created, so
    # we can proceed to run the job. First, make
    # the lock file writable, then store the PID
    # (of this script) and make sure we delete it
    # even if the script gets killed, then we make
    # it read-only again so that the lock file
    # command doesn't get compromised. The next
    # step is to actually execute the program we
    # wanted to execute non-concurrently. We retain
    # the return value of it (the exit status) to
    # pass it on to the calling process when this
    # script terminates

    chmod +w $LOCKFILE    
    # the following two lines come from
    # http://stackoverflow.com/questions/185451/quick-and-dirty-way-to-ensure-only-one-instance-of-a-shell-script-is-running-at-a
    trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT    
    echo $$ > $LOCKFILE
    chmod -w $LOCKFILE    
    
    $PROGRAM "$@"; RETURNVALUE=$?

    rm -f $LOCKFILE
    exit $RETURNVALUE
}

### actual script starts here

if [ $# -lt 1 ]; then
    usage;
elif [ $1 = -h ]; then
    usage;
elif [ $1 = --help ]; then
    usage;
fi

PROGRAM=$(which $1)
shift
LOCKFILE=/tmp/$(escapestr $PROGRAM)_$UID

if lockfile -r 0 $LOCKFILE 2>/dev/null; then
    runsubprocess "$@"
else
    # Something went wrong, the lock file
    # exists. Now check if it isn't a stale lock:
    if kill -0 $(head $LOCKFILE | awk '{ print $1; }') 2>/dev/null; then
	# The lock is indeed stale:	
	echo The process is already running. >&2
	exit 0
    else
	# No running process seems to exist, lock
	# must be stale, so delete it and run the
	# process anew.
	echo Trying to remove stale lock. >&2
	rm -f $LOCKFILE
	if lockfile -r 0 $LOCKFILE 2>/dev/null; then
	    echo Success. Running process. >&2
	    runsubprocess "$@"
	else
	    cat>&2<<EOF
$CMDNAME: Something bad has happened. I was unable
to properly acquire the the lock file
$LOCKFILE. Please investigate.
EOF
	    exit -1
	fi
    fi
fi
Björn Rüffer — Copyright © 2009–2018 — bjoern.rueffer.info