Jay Taylor's notes

back to listing index

linux - How to make sure only one instance of a bash script runs? - Unix & Linux Stack Exchange

[web search]
Original source (unix.stackexchange.com)
Tags: bash linux shell-scripting locking flock atomicity unix.stackexchange.com
Clipped on: 2020-05-31

A solution that does not require additional tools would be prefered.

share | edit | |
asked Sep 18 '12 at 11:04
Image (Asset 2/17) alt=

Almost like nsg's answer: use a lock directory. Directory creation is atomic under linux and unix and *BSD and a lot of other OSes.

if mkdir $LOCKDIR
then
    # Do important, exclusive stuff
    if rmdir $LOCKDIR
    then
        echo "Victory is mine"
    else
        echo "Could not remove lock dir" >&2
    fi
else
    # Handle error condition
    ...
fi

You can put the PID of the locking sh into a file in the lock directory for debugging purposes, but don't fall into the trap of thinking you can check that PID to see if the locking process still executes. Lots of race conditions lie down that path.

share | edit | |
Image (Asset 3/17) alt=

To add to Bruce Ediger's answer, and inspired by this answer, you should also add more smarts to the cleanup to guard against script termination:

#Remove the lock directory
function cleanup {
    if rmdir $LOCKDIR; then
        echo "Finished"
    else
        echo "Failed to remove lock directory '$LOCKDIR'"
        exit 1
    fi
}

if mkdir $LOCKDIR; then
    #Ensure that if we "grabbed a lock", we release it
    #Works for SIGTERM and SIGINT(Ctrl-C)
    trap "cleanup" EXIT

    echo "Acquired lock, running"

    # Processing starts here
else
    echo "Could not create lock directory '$LOCKDIR'"
    exit 1
fi
6

This may be too simplistic, please correct me if I'm wrong. Isn't a simple ps enough?

#!/bin/bash 

me="$(basename "$0")";
running=$(ps h -C "$me" | grep -wv $$ | wc -l);
[[ $running > 1 ]] && exit;

# do stuff below this comment
share | edit | |
answered Sep 18 '12 at 12:19
Image (Asset 5/17) alt=
  • 1
    I've used this condition for a week, and in 2 occasions it didn't prevent new process from starting. I figured what the problem is - new pid is a substring of the old one and gets hidden by grep -v $$. real examples: old - 14532, new - 1453, old - 28858, new - 858. – Naktibalda Feb 22 '18 at 11:30
  • I fixed it by changing grep -v $$ to grep -v "^${$} " – Naktibalda Feb 22 '18 at 11:52
  • @Naktibalda good catch, thanks! You could also fix it with grep -wv "^$$" (see edit). – terdon Feb 22 '18 at 12:38
  • 1
    With this solution, if two instances of the same script are started at the same time, there's a chance that they will "see" each others and both will terminate. It may not be a problem, but it also may be, just be aware of it. – flagg19 Jan 25 at 11:19
  • 5

    One other way to make sure a single instance of bash script runs:

    #!/bin/bash
    
    # Check if another instance of script is running
    pidof -o %PPID -x $0 >/dev/null && echo "ERROR: Script $0 already running" && exit 1
    
    ...

    pidof -o %PPID -x $0 gets the PID of the existing script if its already running or exits with error code 1 if no other script is running

    share | edit | |
    answered Oct 17 '17 at 20:25
    Image (Asset 6/17) alt=

    Although you've asked for a solution without additional tools, this is my favourite way using flock:

    #!/bin/sh
    
    [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
    
    echo "servus!"
    sleep 10

    This comes from the examples section of man flock, which further explains:

    This is useful boilerplate code for shell scripts. Put it at the top of the shell script you want to lock and it'll automatically lock itself on the first run. If the env var $FLOCKER is not set to the shell script that is being run, then execute flock and grab an exclusive non-blocking lock (using the script itself as the lock file) before re-execing itself with the right arguments. It also sets the FLOCKER env var to the right value so it doesn't run again.

    Points to consider:

    See also https://stackoverflow.com/questions/185451/quick-and-dirty-way-to-ensure-only-one-instance-of-a-shell-script-is-running-at.

    share | edit | |
    answered Feb 7 '17 at 20:08
    Image (Asset 7/17) alt=

    I would use a lock file, as mentioned by Marco

    #!/bin/bash
    
    # Exit if /tmp/lock.file exists
    [ -f /tmp/lock.file ] && exit
    
    # Create lock file, sleep 1 sec and verify lock
    echo $$ > /tmp/lock.file
    sleep 1
    [ "x$(cat /tmp/lock.file)" == "x"$$ ] || exit
    
    # Do stuff
    sleep 60
    
    # Remove lock file
    rm /tmp/lock.file
    share | edit | |
    answered Sep 18 '12 at 11:18
    Image (Asset 8/17) alt=
  • 1
    I have never used lsof for this purpose, I this it should work. Note that lsof is really slow in my system (1-2 sec) and most likely there is a lot of time for race conditions. – nsg Sep 18 '12 at 11:45
  • 3

    If you want to make sure that only one instance of your script is running take a look at:

    Lock your script (against parallel run)

    Otherwise you can check ps or invoke lsof <full-path-of-your-script>, since i wouldn't call them additional tools.


    Supplement:

    actually i thought of doing it like this:

    for LINE in `lsof -c <your_script> -F p`; do 
        if [ $$ -gt ${LINE#?} ] ; then
            echo "'$0' is already running" 1>&2
            exit 1;
        fi
    done

    this ensures that only the process with the lowest pid keeps on running even if you fork-and-exec several instances of <your_script> simultaneously.

    share | edit | |
    answered Sep 18 '12 at 11:17
    Image (Asset 9/17) alt=
    2

    This is a modified version of Anselmo's Answer. The idea is to create a read only file descriptor using the bash script itself and use flock to handle the lock.

    SCRIPT=`realpath $0`     # get absolute path to the script itself
    exec 6< "$SCRIPT"        # open bash script using file descriptor 6
    flock -n 6 || { echo "ERROR: script is already running" && exit 1; }   # lock file descriptor 6 OR show error message if script is already running
    
    echo "Run your single instance code here"

    The main difference to all other answer's is that this code doesn't modify the filesystem, uses a very low footprint and doesn't need any cleanup since the file descriptor is closed as soon as the script finishes independent of the exit state. Thus it doesn't matter if the script fails or succeeds.

    share | edit | |
    answered Nov 2 '18 at 5:32
    Image (Asset 10/17) alt=

    I am using cksum to check my script is truly running single instance, even I change filename & file path.

    I am not using trap & lock file, because if my server suddenly down, I need to remove manually lock file after server goes up.

    Note: #!/bin/bash in first line is required for grep ps

    #!/bin/bash
    
    checkinstance(){
       nprog=0
       mysum=$(cksum $0|awk '{print $1}')
       for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
            proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
            if [[ $? -eq 0 ]];then 
               cmd=$(strings /proc/$i/cmdline|grep -v bash)
                    if [[ $? -eq 0 ]];then 
                       fsum=$(cksum /proc/$i/cwd/$cmd|awk '{print $1}')
                       if [[ $mysum -eq $fsum ]];then
                            nprog=$(($nprog+1))
                       fi
                    fi
            fi
       done
    
       if [[ $nprog -gt 1 ]];then
            echo $0 is already running.
            exit
       fi
    }
    
    checkinstance 
    
    #--- run your script bellow 
    
    echo pass
    while true;do sleep 1000;done

    Or you can hardcoded cksum inside your script, so you no worry again if you want to change filename, path, or content of your script.

    #!/bin/bash
    
    mysum=1174212411
    
    checkinstance(){
       nprog=0
       for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
            proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
            if [[ $? -eq 0 ]];then 
               cmd=$(strings /proc/$i/cmdline|grep -v bash)
                    if [[ $? -eq 0 ]];then 
                       fsum=$(grep mysum /proc/$i/cwd/$cmd|head -1|awk -F= '{print $2}')
                       if [[ $mysum -eq $fsum ]];then
                            nprog=$(($nprog+1))
                       fi
                    fi
            fi
       done
    
       if [[ $nprog -gt 1 ]];then
            echo $0 is already running.
            exit
       fi
    }
    
    checkinstance
    
    #--- run your script bellow
    
    echo pass
    while true;do sleep 1000;done
    share | edit | |
    answered May 23 '19 at 23:54
    Image (Asset 11/17) alt=

    Easiest one liner script rather than writing complex PID or lock statements

    flock -xn LOCKFILE.lck -c SCRIPT.SH

    where x denotes exclusive lock and n denotes non-blocking which fails rather than wait for the lock to be released. c runs the script that you want to launch.

    share | edit | |
    answered May 13 at 21:01
    Image (Asset 12/17) alt=

    My code to you

    #!/bin/bash
    
    script_file="$(/bin/readlink -f $0)"
    lock_file=${script_file////_}
    
    function executing {
      echo "'${script_file}' already executing"
      exit 1
    }
    
    (
      flock -n 9 || executing
    
      sleep 10
    
    ) 9> /var/lock/${lock_file}

    Based on man flock, improving only:

    • the name of the lock file, to be based on the full name of the script
    • the message executing

    Where I put here the sleep 10, you can put all the main script.

    share | edit | |
    answered Sep 3 '18 at 20:38
    Image (Asset 13/17) alt=

    This handy package does what you're looking for.

    https://github.com/krezreb/singleton

    once installed, just prefix your command with singleton LOCKNAME

    e.g. singleton LOCKNAME PROGRAM ARGS...

    share | edit | |
    answered Feb 19 at 18:07
    Image (Asset 14/17) alt=

    You can use this: https://github.com/sayanarijit/pidlock

    sudo pip install -U pidlock
    
    pidlock -n sleepy_script -c 'sleep 10'
    share | edit | |
    answered Jan 12 '18 at 18:32

    Hot Network Questions

    Linux is a registered trademark of Linus Torvalds. UNIX is a registered trademark of The Open Group.

    This site is not affiliated with Linus Torvalds or The Open Group in any way.