Domanda

When running a shell script I want to be absolutely sure that just a single instance of it runs at a given time.

Should I employ a file lock mechanism, like in

touch lockfile
do_work
rm lockfile

Is that enough?

È stato utile?

Soluzione 3

It think that your logic is more like:

if [ ! -e lockfile ]; then
    touch lockfile
    do_work
    rm lockfile
fi

But be aware that even this is not enough. Indeed, it paves the way for subtle bugs. Since the whole thing is not atomic, a second process might as well start just after the if clause but before the touch one.

A less naive implementation of your idea would attempt at atomicity of the lock-acquiring operation. This can be done in Bash.

A possible solution is the noclobber option, which forbids writing to an already existing file, thereby accomplishing both the if clause and the touch command in a more-of-an-atomic fashion.

My lock-acquiring code would then look like this:

if ! ( set -o noclobber; echo > lockfile ) exit

Altri suggerimenti

You can use flock for this :

 (
  flock -s 200
  # .... commands executed under lock....
 )200>/var/lock/mylockfile

It is available under the Util-linux next generation packaging... http://en.wikipedia.org/wiki/Util-linux

Since flock is not installed on some systems I use—and I jump between Ubuntu (which has it) and Mac OS X (which does not) a lot—I use this simple framework without any real issue:

LOCK_NAME="MY_GREAT_BASH_SCRIPT"
LOCK_DIR='/tmp/'${LOCK_NAME}.lock
PID_FILE=${LOCK_DIR}'/'${LOCK_NAME}'.pid'

if mkdir ${LOCK_DIR} 2>/dev/null; then
    # If the ${LOCK_DIR} doesn't exist, then start working & store the ${PID_FILE}
    echo $$ > ${PID_FILE}

    echo "Hello world!"

    rm -rf ${LOCK_DIR}
    exit
else
    if [ -f ${PID_FILE} ] && kill -0 $(cat ${PID_FILE}) 2>/dev/null; then
        # Confirm that the process file exists & a process
        # with that PID is truly running.
        echo "Running [PID "$(cat ${PID_FILE})"]" >&2
        exit
    else
        # If the process is not running, yet there is a PID file--like in the case
        # of a crash or sudden reboot--then get rid of the ${LOCK_DIR}
        rm -rf ${LOCK_DIR}
        exit
    fi
fi

The idea is that general core—where I have echo "Hello world!"—is where the heart of your script is. The rest of it is basically a locking mechanism based on mkdir. A good explanation of the concept is in this answer:

mkdir creates a directory if it doesn't exist yet, and if it does, it sets an exit code. More importantly, it does all this in a single atomic action making it perfect for this scenario.

Either creates a lock file or fails (no race condition possible) while conveniently recording the shell's PID in the program's lock file. It Then cleans up if program is given any of the listed signals

#!/bin/bash

# Garbage clean up with trap given any of these SIGNALS
# INT   Pressing Control-C      kill -2 pid
# TERM  Termination signal      kill -15 pid
# EXIT  Shell exit

LockFile="/tmp/${0}.lock" 
if ( set -o noclobber; echo "$$" > "$LockFile") 2> /dev/null; then
    trap 'rm -f "$LockFile"; exit $?' INT TERM EXIT
else
    echo -e "Program \"$0\" already running"
    exit 1
fi
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top