How Can I Tell If My Bash Script Is Already Running?

A lot of times I have to write Bash scripts that get scheduled to run over and over again. These scripts can also take a while to run depending on the task I have for it. There could be a huge problem if I crank up multiple versions of the same script on accident and they are working on the same files and/or directories. So to solve this problem I’ll use a technique that creates lock files when the script is running and then removes it when it is finished running. Then I can just check for the existence of my lock file before I run the script and exit if it is already running.

To keep things nice and tidy I usually have a section to declare and define all of my variables. This is where I specify what the name of the lock file is going to be. I will generally base the lock file on the name of the currently executing script and just append a .lck file extension.

Now the very first thing I do in my file is check for the existence of my lock file. The -f option checks that the given file name is a regular file (not a directory or device). If it does not exist then I make the assumption that my script is not running and I create the lock file. Notice that I use the $$ system variable which returns the PID (process ID) of the currently running script. I simply echo this value into the lock file.

Well, if the file does exist then we need to make doubly sure that it is running. Why? Well the script may have terminated prematurely and not have had a chance to cleanup the lock file. So I grab the PID value out of the lock file and then use the ps command to see if a process with that PID is actually running. If it is not running then I again just echo $$ out to the lock file. If it is running I just exit.

Well, that’s pretty much it! Now if you have to cron (schedule) a Bash script, you can rest assured that you won’t have multiple copies running at the same time. Here is the entire test script that I used. You can copy & paste it to use as a template for creating your own.

  • Soren

    If I may be pedantic, there is a possible race condition here.
    Suppose two processes start simultaneously with no lock file prior.
    Suppose they both meet the lock check condition at the same time. In that case both processes would be granted access to the code.
    After writing the lock you could write protect it and afterwards check if the content matches the process id. Something like:
    echo $$ > “${LCK_FILE}”
    chmod -w “${LCK_FILE}”
    LOCK=cat "${LCK_FILE}"
    if [ $LOCK != $$ ];
    then
    echo “Whoops”
    exit 1
    fi

  • Thanks, Soren for the observation. You are in fact correct. I haven’t actually run across this problem in my experience though because of the processing that I use this for.

    I think your solution is a good one though and I’ll probably add it to my scripts when I get a chance.

  • Michael Kennedy

    Jonathan, Soren,
    I think Soren highlights the “race” very well, but the solution may need to be extended. For example, a 2nd session might try to interrupt the main session:
    – between Soren’s “echo” and “chmod”, or
    – between the “chmod” and the “cat”, or
    – between the cat and the “if”.
    In an extreme situation, the 2nd session might even then perform the entire “function”, and delete the darn LCK-FILE from under the nose of session-1!.

    So, I think a “100%” trap for the race is even more complicated. I see references to utilities like LOCKF and LOCKFILE, which, AFAIK, are designed to address these issues.
    – Mike

  • Thanks for your response Michael. While i believe you are in fact correct about there still being a possibility for a race condition… this possibility falls within the tolerances that I personally am willing to accept. For the scripts that I run this would be such a remote situation that I’m cool with it.

    I do appreciate your contributions to this discussion though. 🙂

  • Pingback: How Can I Tell If My Bash Script Is Already Running? - Part 2 | franzone.com()

  • Nico Halpern

    This seems to be a simple mutex problem. The way you resolve the race condition between lock checking and locking is with a ‘mkdir’ type lock. mkdir() is atomic in UNIX so you are guaranteed try_lock() behaviour. This is what I woud try:

    mkdir “$PDIR/lock”
    if [ “$?” -ne 0 ];
    then
    echo “Could not lock…”
    fi

    #succeeded “locking”
    do_stuff()
    rmdir -f “$PDIR/lock”

  • That is a very interesting take on this problem that I have never seen. Thanks for contributing to the discussion! I have actually started using the flock program to create locks in a very simple manner. You can see the details about that on Part 2 to this post.

  • Dominic

    What is wrong with this approach which does not use a lock file at all:

    if [ ! -z “ps -A -o "pid,ppid,comm"| grep " basename $0$" | grep -v " $$ "” ]; then
    #script is already running – abort
    exit 1
    fi

    This looks for running processes, filters for those with the same name as this script, then filters out any with the same pid or parent pid as this one. If the resulting string is not zero length then the script is already running and we should abort. It works for me.

  • That’s another great idea! Thanks for commenting Dominic.

  • Dominic

    Here is a refinement of my earlier suggestion, which works (even) better. You use this code snippet at the start of a script to see if another instance of the script is already running. The first command (ps) looks for running processes with the same name as this script, then the next (grep) filters out processes which have the same pid, parent pid or sid as this instance, and then the last (grep) filters out processes that are defunct. If any process is still left then it must be another active instance of this script, and we should abort.

    if [ ! -z “ps -C basename $0 --no-headers -o "pid,ppid,sid,comm"|grep -v "$$ "|grep -v ""” ]; then
    #script is already running – abort
    exit 1
    fi

  • Jonathan, Dominic,

    [It’s over a year since I posted here, so one is overdue!!].

    Re Dominic’s most recent version, I tried it, and had problems with the “Defunct” check (which I don’t understand anyway!). When the 2nd instance starts up:
    – the PS command correctly extracted the pid/ppid/etc of the 2 x instances.
    – the GREP -v “$$ ” operation on PID correctly discarded the 2nd instance.
    – the defunct GREP -v “” operation discarded the 1st instance line, leaving an empty response, and thereby the overall duplicate instance was not detected.

    Maybe, if I understood the “defunct” issues, I could help further!

    Best regards,
    – Mike

  • Folks,
    I had a look at “defunct/zombie” processes, and maybe the following tweaks would do the trick:

    if [ ! -z “ps -C basename $0 --no-headers -o "pid,ppid,sid,stat,comm"|grep -v "$$ "|grep -v " Z "” ]; then
    #script is already running – abort
    exit 1
    fi

    This includes the “stat” column in the data going from PS to GREP-1. A “Z” in that field indicates a Zombie process, and these are then excluded from the list. Anything left = “Dupe Launch”.

    With the space-Z-space test on the 2nd GREP, and if the BASH script happened to be named just “Z”, then, can someone confirm:
    – a running instance would appear as “nnn nnn nnn R Z”, and the 2nd GREP would not find a space after the “Z”, and would not purge that entry from the list, correctly indicating “Dupe”.
    – a defunct instance would show as “nnn nnn nnn Z Z”. The 2nd GREP would match on the space-Z-space, clear it from the list, correctly indicating “Not a Dupe”.
    Hopefully!!
    –Mike

    — Mike

  • Soren

    Hi, crazy to see this thread still alive after more than a year,

    If it is not possible, for some reason, to use some of the improved alternatives proposed, I will like to defend my original locking in comment 1. Michael Kennedy suggests a possible race condition in comment 3, but I don’t think that is possible, since the process number is being reread in the LOCK=cat “${LCK_FILE}” statement after it has been write protected. If a possible racing second script would override the process id in the file before the read only line, then there would be a mismatch in the following if after the id was read again from the file.

    Anyway I would probably in most cases go for the flock method, the ps hack or similar more elegant solutions.

    Soren

  • Dominic

    Hi all

    Sorry for the confusion. My code was right as I wrote it in posting 9 but it came out wrong on the web page because it involves ‘greater than’ and ‘less than’ codes on either side of the word ‘defunct’ after the first inverted comma after the last grep (er got that?). I’m not sure how to get it to come out correct on this page, but I’ll try below. Anyway seems like Mike’s approach works as good as mine, the ‘Z’ in stat column is same (I think) as ‘defunct’ in comm column. Maybe in Mike’s the comm column is not needed at all?

    This is the solution I intended to show in comment 9, hope it comes out right this time:

    if [ ! -z “ps -C basename $0 --no-headers -o "pid,ppid,sid,comm"|grep -v "$$ "|grep -v "<defunct>"” ]; then
    #script is already running – abort
    exit 1
    fi

  • Thanks, Dominic. That’s much better!
    My basic problem is that I’ve not seen “defunct/zombie” processes anyway, so I’m kinda “shooting in the dark” here. If the “comment” on zombies processes has both “defunct” and the process name, then the PS will extract the matches on the script name, and GREP#2 will also work fine.
    With my “fix”, I need the “comment” column, just to ensure there’s a space after the “Z” status. Also, it might be comforting to see the actual name on the extracted list, if one ever goes digging…
    Finally, I think your approach will ALWAYS catch dupes, even in extreme timing conditions – eg, process#2 started N nono-secs after process#1, etc, etc, and will never get into any “race conditions”, etc.
    Thank you for a very very nice solution.
    – Mike

  • Dominic

    Thanks for your input Mike.

    As you guessed, if a script has been stopped (e.g. with kill) then it seems to hang around a bit in the ps listing and comm column reports the scriptname followed by space and (at least it does on mine). But according to ‘man ps’ the Z status in comm column does the same thing, so yours should work too (but I haven’t tested it). This is particularly likely to happen if you are working on the script, killing one instance and wanting to start another. The script lets you do this without having to wait for the first killed instance to disappear.

  • Dominic

    Yet again I am thrown by the way this website handles > and <, you have to enter them with ampersand gt;, and ampersand lt;, backslashes don’t work! So my text above should have read:Thanks for your input Mike.

    As you guessed, if a script has been stopped (e.g. with kill) then it seems to hang around a bit in the ps listing and comm column reports the scriptname followed by space and <defunct> (at least it does on mine). But according to ‘man ps’ the Z status in comm column does the same thing, so yours should work too (but I haven’t tested it). This is particularly likely to happen if you are working on the script, killing one instance and wanting to start another. The script lets you do this without having to wait for the first killed instance to disappear.

  • Sorry about the less than / greater than signs, guys! 🙁 This is plain vanilla install of WordPress without many plugins or mods.

  • Pingback: Standard Mischief » common bash misconception()

  • Peter-Alexander

    I tried to achieve the same and had the ps command in mind. While struggling with syntax I began searching the web and gladly found this site. Thank you all for posting here. The solution from Dominic in post 13 works perfect for me – I could just copy&paste it.

    I must admit the flock command looks very nice too but requires to use it when calling the script.
    Peter

  • Thanks for the great tips guys. Used Dominic’s method in #13 that worked first time (I also tried the OP which worked for me first time). I’m a bash noob so really appreciate the sharing 🙂

  • Yeah, thanks! This really helped me out. 🙂