Objective: Ensure that only a single instance of a shell script is running at a time on Linux.
To allow only a single instance of a shell script to run at a given time, we will need to use file locking. File locking is a mechanism that restricts access to a computer file by allowing only one user or process access at any specific time. Many file lock implementations do not work well as they do not cater for race conditions. Let’s take a look at a few examples.
A typical example of using simple file locks in shell scripts is shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
lockfile=/path/to/script.lock # remove lock file during exit trap "rm -f $lockfile; exit" INT TERM EXIT if [ -f "$lockfile" ] ; then # lock is already held echo >&2 "lock not acquired, giving up: $lockfile" exit 1 else # nobody owns the lock > "$lockfile" # create the lock file # ... commands executed under lock / single instance ... echo "lock acquired: $lockfile" fi |
The above code will look for a lock file and if it does not exist, it will create a lock file and assumes it has exclusive rights to the file. But there is a race condition involved as the commands are not atomic in nature. There is a time window between checking and creating the file, during which other programs may act. If two processes run the same code at the same time, both processes will determine that the lock file does not exist and both processes will assume that they have acquired the lock.
Also, if a script has acquired the lock and if the process gets killed using signal 9 (SIGKILL
), we may get hit with a stale lock problem – the lock file will not be removed and other processes will have the false impression that the lock is still being held by another process.
On Linux, we can mitigate this problem using flock
. Note that flock
is not standardised by POSIX and it may/will not work well on NFS filesystems.
1 2 3 4 5 6 7 8 |
lockfile=/path/to/script.lock noflock () { echo >&2 "lock not acquired, giving up: $lockfile" ; exit 1 ; } ( flock -n 9 || noflock # ... commands executed under lock / single instance ... echo "lock acquired: $lockfile" ) 9>$lockfile |
The above syntax will create a lock file using FD (file descriptor) 9 and tries to acquire a lock to the file in a non-blocking manner. If the lock is not acquired, flock
will return an exit status of 1. In this case, the noflock
function is executed – display an error and exit. If the lock
is acquired, flock
will return 0.
1 |
flock -u 9 |