2

I would like to run a cron job. Scheduling it is easy and works.

My problem is that I don't know how to only have one version of itself running at a time.

Any ideas?

G-Man

6 Answers 6

10

My apologies to embobo, but I copied his script and modified it to eliminate the race condition that I noted in my comment to his answer:

#!/bin/sh
LOCKDIR=/var/run/myprog.lock
mkdir --mode=700 $LOCKDIR || exit 0
trap "{ rm -rf $LOCKDIR ; exit 0; }" EXIT
# do stuff

Basically, we are counting on the fact that directory creation is atomic, so that only one copy of this script running concurrently can successfully create $LOCKDIR. It's probably best to make sure that $LOCKDIR is on a local drive, not on an NFS disk or a Samba mount, etc.

1
  • If the system crashs the lock dir will be there but the process is not running anymore. Am I wrong? Maybe something like pidof would work better? May 16, 2017 at 12:35
4

You could use a lock file. Example shell code:

#!/bin/sh
LOCKFILE=/var/run/myprog.pid
[ -f $LOCKFILE ] && exit 0
trap "{ rm -f $LOCKFILE ; exit 0; }" EXIT
echo "$$" > $LOCKFILE
# do stuff

It is important to make sure that the lock file gets removed at any kind of exit (normal or abnormal). That is what the trap command does.

You could make it more sophisticated by doing more if the lock file exists. For example, get the pid from the file and see if it is actually running.

1
  • This solution has a race condition. The problem is that between the existence test (line 3) and the lockfile creation (line 5), a context switch could allow another copy of the script to run, thus allowing two (or more) copies of the script to run concurrently. Although this answer will probably never hit the race condition when run in the context of cron, see my answer for an alternate solution that is not affected by the race condition. Oct 14, 2010 at 21:04
2

Just have your script touch a file when it starts and delete that file when it completes. Then within the script, you can check for the existence of that file before doing anything.

In pseudocode:

if (!exists(/var/run/filename))
{
    touch /var/run/filename;
    do stuff;
    rm /var/run/filename;
}
0
1

You can do the above where you create a lock file or directory. I personally open a socket connection to some high port. This ensures that even if the application crashes the socket will be closed. With a lock file the file can continue and your script will not run till you manually remove it.

In my experience this has proven to be more reliable.

1
  • I like this idea! Could you share an example? Specifically, I would be interested in seeing it done in the Bash shell. Oct 18, 2010 at 21:55
1

There is a "lockfile" binary installed on most systems that you can use to avoid getting into the game of bash scripting oneupsmanship. On RHEL/CentOS/Fedora its part of the procmail package.

So you could put something like:
lockfile /var/lock/mycron.lock && /usr/local/bin/mycron && rm -f /var/lock/mycron.lock

There's more options in the man page, you can set it to wait some amount of time, to clean up after itself, etc.

0

I seem to succeed with to following calling script:

#!/bin/zsh
RED_="\033[31m"
BLUE_="\033[34m"
BLACK_="\033[0m"
if [ $# -lt 1 ]; then
 echo "$0: Invoke a program/script only once at a time"
 echo "Syntax: $0 <program/script with parameters>"
else
 if ps -Alf | grep "$*" | grep -v grep | grep -v $0 >/dev/null; then
  echo "$BLUE_\"$@\"$RED_ runs already. I don't invoke it.$BLACK_"
 else
  echo "${RED_}I invoke: $BLUE_$@$BLACK_"
  $@
 fi
fi
1
  • Welcome to Server Fault. Questions and answers here must be in English, so I have translated this for you. Oct 26, 2014 at 15:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .