tags:

views:

468

answers:

6

What is the simplest/best way to ensure only 1 copy of given script is running - assuming it's bash on linux?

At the moment I'm doing:

ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh

but it has several issues:

  1. it puts the check outside of script
  2. it doesn't let me run the same script from separate accounts - which I would like sometimes.
  3. -C checks only first 14 characters of process name

Of course I can write my own pidfile handling, but I sense that there should be some simple way to do it.

+9  A: 

If the script is the same across all users, you can use a lockfile approach. If you acquire the lock, proceed else show a message and exit.

As an example:

[Terminal #1] $ lockfile -r 0 /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] lockfile: Sorry, giving up on "/tmp/the.lock"

[Terminal #1] $ rm -f /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] $

After /tmp/the.lock has been acquired your script will be the only one with access to execution. When you are done, just remove the lock. In script form this might look like:

#!/bin/bash

lockfile -r 0 /tmp/the.lock || exit 1

# Do stuff here

rm -f /tmp/the.lock
ezpz
+1. Even if behavior differs across users, OP could use `lockfile`. Just have a separate lockfile for each user or group that's allowed to run their own instance.
outis
Can we have an example code snippet?
martin clayton
Added an example and skeleton script.
ezpz
I don't have lockfile program on my linux, but one thing bothers me - will it work if first script will die without removing lock? i.e. in such case i want next run of script to run, and not die "because previous copy is still working"
depesz
That would involve some notion of `try` / `catch`. This is not impossible to fake, but AFAIK is not directly implemented in bash. Any (bash) solution will be confronted with this same problem, however...
ezpz
@depesz - if you make your lockfile name include the process id somehow (maybe filename.$$) you can check whether a lockfile has gone stale. I'm not saying it's pretty though...
martin clayton
Shannon Nelson
+1  A: 

from with your script:

ps -ef | grep $0 | grep $(whoami)
ennuikiller
This has the relatively well known bug with grep finding itself. Of course I can work around it, but it's not something I would call simple and robust.
depesz
I've seen many 'grep -v grep's. Your ps might support -u $LOGNAME too.
martin clayton
it's relatively robust in that its uses $0 and whoami to ensure your gettinmg only the script started by your userid
ennuikiller
ennuikiller: no - grep $0 will find processes like $0 (for example the one that is running this ps right now), but it will *also* find a grep itself! so basically - it will virtually always succeed.
depesz
@depesz, yes of course I'm assuming your doing grep -v grep as well!
ennuikiller
That's not a bug, that's a feature! Also, `ps -ef | grep [\ ]$0` eliminates finding the grep.
Dennis Williamson
@ennuikiller: that assumption was not in your example. besides - it will find "call.sh" even in things like "call.sh". and it will also fail if i'll call it from ./call.sh itself (it will find the call.sh copy that is doing the check, not some previous) - so. in short - this is not solution. it can be changed to be solution by adding at least 2 more greps, or changing existing one, but it doesn't on its own solve the problem.
depesz
+1  A: 

I'm not sure there's any one-line robust solution, so you might end up rolling your own.

Lockfiles are imperfect, but less so than using 'ps | grep | grep -v' pipelines.

Having said that, you might consider keeping the process control separate from your script - have a start script. Or, at least factor it out to functions held in a separate file, so you might in the caller script have:

. my_script_control.ksh

# Function exits if cannot start due to lockfile or prior running instance.
my_start_me_up lockfile_name;
trap "rm -f $lockfile_name; exit" 0 2 3 15

in each script that needs the control logic. The trap ensures that the lockfile gets removed when the caller exits, so you don't have to code this on each exit point in the script.

Using a separate control script means that you can sanity check for edge cases: remove stale log files, verify that the lockfile is associated correctly with a currently running instance of the script, give an option to kill the running process, and so on. It also means you've got a better chance of using grep on ps output successfully. A ps-grep can be used to verify that a lockfile has a running process associated with it. Perhaps you could name your lockfiles in some way to include information about the process: user, pid, etc., which can be used by a later script invocation to decide whether the process that created the lockfile is still around.

martin clayton
A: 

I found a pretty simple way to handle "one copy of script per system". It doesn't allow me to run multiple copies of the script from many accounts though (on standard Linux that is).

Solution:

At the beginning of script, I gave:

pidof -s -o '%PPID' -x $( basename $0 ) > /dev/null 2>&1 && exit

Apparently pidof works great in a way that:

  • it doesn't have limit on program name like ps -C ...
  • it doesn't require me to do grep -v grep ( or anything similar )

And it doesn't rely on lockfiles, which for me is a big win, because relaying on them means you have to add handling of stale lockfiles - which is not really complicated, but if it can be avoided - why not?

As for checking with "one copy of script per running user", i wrote this, but I'm not overly happy with it:

(
    pidof -s -o '%PPID' -x $( basename $0 ) | tr ' ' '\n'
    ps xo pid= | tr -cd '[0-9\n]'
) | sort | uniq -d

and then I check its output - if it's empty - there are no copies of the script from same user.

depesz
A: 

Advisory locking have been used for ages and it can be used in bash scripts. I prefer simple flock (from util-linux[-ng]) over lockfile (from procmail). And always remember about a trap on exit (sigspec == EXIT or 0, trapping specific signals is superfluous) in those scripts.

In September I made public my lockable script boilerplate. Transforming this into one-instance-per-user is trivial. Using it you can also easily write scripts for other scenarios requiring some locking or synchronization.

przemoc
A: 

Ubuntu/Debian distros have the start-stop-daemon tool which is for the same purpose you describe. See also /etc/init.d/skeleton to see how it is used in writing start/stop scripts.

-- Noah

Noah Spurrier