Quick-and-dirty way to ensure only one instance of a shell script is running at a time

前端 未结 30 2454
忘掉有多难
忘掉有多难 2020-11-22 02:57

What\'s a quick-and-dirty way to make sure that only one instance of a shell script is running at a given time?

30条回答
  •  心在旅途
    2020-11-22 03:23

    I use a simple approach that handles stale lock files.

    Note that some of the above solutions that store the pid, ignore the fact that the pid can wrap around. So - just checking if there is a valid process with the stored pid is not enough, especially for long running scripts.

    I use noclobber to make sure only one script can open and write to the lock file at one time. Further, I store enough information to uniquely identify a process in the lockfile. I define the set of data to uniquely identify a process to be pid,ppid,lstart.

    When a new script starts up, if it fails to create the lock file, it then verifies that the process that created the lock file is still around. If not, we assume the original process died an ungraceful death, and left a stale lock file. The new script then takes ownership of the lock file, and all is well the world, again.

    Should work with multiple shells across multiple platforms. Fast, portable and simple.

    #!/usr/bin/env sh
    # Author: rouble
    
    LOCKFILE=/var/tmp/lockfile #customize this line
    
    trap release INT TERM EXIT
    
    # Creates a lockfile. Sets global variable $ACQUIRED to true on success.
    # 
    # Returns 0 if it is successfully able to create lockfile.
    acquire () {
        set -C #Shell noclobber option. If file exists, > will fail.
        UUID=`ps -eo pid,ppid,lstart $$ | tail -1`
        if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
            ACQUIRED="TRUE"
            return 0
        else
            if [ -e $LOCKFILE ]; then 
                # We may be dealing with a stale lock file.
                # Bring out the magnifying glass. 
                CURRENT_UUID_FROM_LOCKFILE=`cat $LOCKFILE`
                CURRENT_PID_FROM_LOCKFILE=`cat $LOCKFILE | cut -f 1 -d " "`
                CURRENT_UUID_FROM_PS=`ps -eo pid,ppid,lstart $CURRENT_PID_FROM_LOCKFILE | tail -1`
                if [ "$CURRENT_UUID_FROM_LOCKFILE" == "$CURRENT_UUID_FROM_PS" ]; then 
                    echo "Script already running with following identification: $CURRENT_UUID_FROM_LOCKFILE" >&2
                    return 1
                else
                    # The process that created this lock file died an ungraceful death. 
                    # Take ownership of the lock file.
                    echo "The process $CURRENT_UUID_FROM_LOCKFILE is no longer around. Taking ownership of $LOCKFILE"
                    release "FORCE"
                    if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
                        ACQUIRED="TRUE"
                        return 0
                    else
                        echo "Cannot write to $LOCKFILE. Error." >&2
                        return 1
                    fi
                fi
            else
                echo "Do you have write permissons to $LOCKFILE ?" >&2
                return 1
            fi
        fi
    }
    
    # Removes the lock file only if this script created it ($ACQUIRED is set), 
    # OR, if we are removing a stale lock file (first parameter is "FORCE") 
    release () {
        #Destroy lock file. Take no prisoners.
        if [ "$ACQUIRED" ] || [ "$1" == "FORCE" ]; then
            rm -f $LOCKFILE
        fi
    }
    
    # Test code
    # int main( int argc, const char* argv[] )
    echo "Acquring lock."
    acquire
    if [ $? -eq 0 ]; then 
        echo "Acquired lock."
        read -p "Press [Enter] key to release lock..."
        release
        echo "Released lock."
    else
        echo "Unable to acquire lock."
    fi
    

提交回复
热议问题