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

前端 未结 30 3217
忘掉有多难
忘掉有多难 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:21

    For shell scripts, I tend to go with the mkdir over flock as it makes the locks more portable.

    Either way, using set -e isn't enough. That only exits the script if any command fails. Your locks will still be left behind.

    For proper lock cleanup, you really should set your traps to something like this psuedo code (lifted, simplified and untested but from actively used scripts) :

    #=======================================================================
    # Predefined Global Variables
    #=======================================================================
    
    TMPDIR=/tmp/myapp
    [[ ! -d $TMP_DIR ]] \
        && mkdir -p $TMP_DIR \
        && chmod 700 $TMPDIR
    
    LOCK_DIR=$TMP_DIR/lock
    
    #=======================================================================
    # Functions
    #=======================================================================
    
    function mklock {
        __lockdir="$LOCK_DIR/$(date +%s.%N).$$" # Private Global. Use Epoch.Nano.PID
    
        # If it can create $LOCK_DIR then no other instance is running
        if $(mkdir $LOCK_DIR)
        then
            mkdir $__lockdir  # create this instance's specific lock in queue
            LOCK_EXISTS=true  # Global
        else
            echo "FATAL: Lock already exists. Another copy is running or manually lock clean up required."
            exit 1001  # Or work out some sleep_while_execution_lock elsewhere
        fi
    }
    
    function rmlock {
        [[ ! -d $__lockdir ]] \
            && echo "WARNING: Lock is missing. $__lockdir does not exist" \
            || rmdir $__lockdir
    }
    
    #-----------------------------------------------------------------------
    # Private Signal Traps Functions {{{2
    #
    # DANGER: SIGKILL cannot be trapped. So, try not to `kill -9 PID` or 
    #         there will be *NO CLEAN UP*. You'll have to manually remove 
    #         any locks in place.
    #-----------------------------------------------------------------------
    function __sig_exit {
    
        # Place your clean up logic here 
    
        # Remove the LOCK
        [[ -n $LOCK_EXISTS ]] && rmlock
    }
    
    function __sig_int {
        echo "WARNING: SIGINT caught"    
        exit 1002
    }
    
    function __sig_quit {
        echo "SIGQUIT caught"
        exit 1003
    }
    
    function __sig_term {
        echo "WARNING: SIGTERM caught"    
        exit 1015
    }
    
    #=======================================================================
    # Main
    #=======================================================================
    
    # Set TRAPs
    trap __sig_exit EXIT    # SIGEXIT
    trap __sig_int INT      # SIGINT
    trap __sig_quit QUIT    # SIGQUIT
    trap __sig_term TERM    # SIGTERM
    
    mklock
    
    # CODE
    
    exit # No need for cleanup code here being in the __sig_exit trap function
    

    Here's what will happen. All traps will produce an exit so the function __sig_exit will always happen (barring a SIGKILL) which cleans up your locks.

    Note: my exit values are not low values. Why? Various batch processing systems make or have expectations of the numbers 0 through 31. Setting them to something else, I can have my scripts and batch streams react accordingly to the previous batch job or script.

提交回复
热议问题