/ Tech

Easy bash script locking with mkdir

It's very easy to implement locking in a bash script using mkdir.

While there are other methods like:

  • Touch a file and check its availability
  • Use special tools like lockfile-progs in Debian or lockfile in other distributions
  • Find the running process with pgrep

I prefer mkdir for simple bash scripts which only should run once at any given time. A folder can only be created once with the same name, if mkdir is told to create this folder again, it ends with an error.
This error can be used to check if a script is already running. Of course there should be some cleanup if a script terminates before it cleanly ends or the script will never run again until you manually delete the folder. This can be done with traps, which executes cleanup functions on signals.

Here is an skeleton bash script which provides simple locking:

#!/bin/bash -
SCRIPTNAME=$(basename $0)
LOCK_DIR="/var/lock/${SCRIPTNAME}"
PIDFILE="${LOCK_DIR}/PID"

function lock {
  mkdir $LOCK_DIR 2> /dev/null
  [ $? == 0 ] && echo $$ > $PIDFILE
}
function remove_lock {
  rm -rf $LOCK_DIR
}
function exit_on_error {
  echo "Error, cleanup..."
  remove_lock
  UNLOCK=1
  exit 1
}
function exit_on_kill {
  echo "Kill, cleanup..."
  remove_lock
  UNLOCK=1
  exit 1
}
function exit_on_end {
  if [ "$UNLOCK" != "0" ]; then
    echo "End, cleanup..."
    remove_lock
    UNLOCK=1
  fi
  exit $1
}
function lock_failed {
  # check for PID
  PID=$(cat $PIDFILE)

  # Maybe there was an error reading the PID file
  if [ $? != 0 ]; then
   echo "Locking failed, PID ${PID} is active" >&2
   UNLOCK=1
   exit 1
  fi

  # Check if the PID is really existing
  if ! kill -0 $PID &>/dev/null; then
    echo "Removing stale lock of nonexistant PID ${PID}" >&2
    remove_lock
    echo "Restarting myself (${SCRIPTNAME})" >&2
    exec "$0" "$@"
  else
    echo "Locking failed, PID ${PID} is active" >&2
    UNLOCK=1
    exit 1
  fi
  UNLOCK=1
  exit 1
}

trap exit_on_kill KILL
trap exit_on_error ERR
trap 'exit_on_end $?' QUIT TERM EXIT INT

lock || lock_failed

### My script:

echo "Hello World"
sleep 10

This skeleton is inspired by the following articles: Lock your script (against parallel run) and Manage locking into bash scripts. Thanks!

Some things needs explanations:

  • At the end of every bash script, the Trap handler 0 is called. Using the variable UNLOCK I can check if exit_on_end is called the second time. Otherwise everytime the script ends the lock is removed, regardless if it should or not.
  • exit_on_end is called with the current exit code when it's called to preserve it
  • If the cleanup did not succeed, the check if the PID really exists can handle this case

Start your script below ### My script: and have some fun.