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

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

    Really quick and really dirty? This one-liner on the top of your script will work:

    [[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
    

    Of course, just make sure that your script name is unique. :)

    0 讨论(0)
  • 2020-11-22 03:14

    PID and lockfiles are definitely the most reliable. When you attempt to run the program, it can check for the lockfile which and if it exists, it can use ps to see if the process is still running. If it's not, the script can start, updating the PID in the lockfile to its own.

    0 讨论(0)
  • 2020-11-22 03:16

    The existing answers posted either rely on the CLI utility flock or do not properly secure the lock file. The flock utility is not available on all non-Linux systems (i.e. FreeBSD), and does not work properly on NFS.

    In my early days of system administration and system development, I was told that a safe and relatively portable method of creating a lock file was to create a temp file using mkemp(3) or mkemp(1), write identifying information to the temp file (i.e. PID), then hard link the temp file to the lock file. If the link was successful, then you have successfully obtained the lock.

    When using locks in shell scripts, I typically place an obtain_lock() function in a shared profile and then source it from the scripts. Below is an example of my lock function:

    obtain_lock()
    {
      LOCK="${1}"
      LOCKDIR="$(dirname "${LOCK}")"
      LOCKFILE="$(basename "${LOCK}")"
    
      # create temp lock file
      TMPLOCK=$(mktemp -p "${LOCKDIR}" "${LOCKFILE}XXXXXX" 2> /dev/null)
      if test "x${TMPLOCK}" == "x";then
         echo "unable to create temporary file with mktemp" 1>&2
         return 1
      fi
      echo "$$" > "${TMPLOCK}"
    
      # attempt to obtain lock file
      ln "${TMPLOCK}" "${LOCK}" 2> /dev/null
      if test $? -ne 0;then
         rm -f "${TMPLOCK}"
         echo "unable to obtain lockfile" 1>&2
         if test -f "${LOCK}";then
            echo "current lock information held by: $(cat "${LOCK}")" 1>&2
         fi
         return 2
      fi
      rm -f "${TMPLOCK}"
    
      return 0;
    };
    

    The following is an example of how to use the lock function:

    #!/bin/sh
    
    . /path/to/locking/profile.sh
    PROG_LOCKFILE="/tmp/myprog.lock"
    
    clean_up()
    {
      rm -f "${PROG_LOCKFILE}"
    }
    
    obtain_lock "${PROG_LOCKFILE}"
    if test $? -ne 0;then
       exit 1
    fi
    trap clean_up SIGHUP SIGINT SIGTERM
    
    # bulk of script
    
    clean_up
    exit 0
    # end of script
    

    Remember to call clean_up at any exit points in your script.

    I've used the above in both Linux and FreeBSD environments.

    0 讨论(0)
  • 2020-11-22 03:16

    Using the process's lock is much stronger and takes care of the ungraceful exits also. lock_file is kept open as long as the process is running. It will be closed (by shell) once the process exists (even if it gets killed). I found this to be very efficient:

    lock_file=/tmp/`basename $0`.lock
    
    if fuser $lock_file > /dev/null 2>&1; then
        echo "WARNING: Other instance of $(basename $0) running."
        exit 1
    fi
    exec 3> $lock_file 
    
    0 讨论(0)
  • 2020-11-22 03:19

    An example with flock(1) but without subshell. flock()ed file /tmp/foo is never removed, but that doesn't matter as it gets flock() and un-flock()ed.

    #!/bin/bash
    
    exec 9<> /tmp/foo
    flock -n 9
    RET=$?
    if [[ $RET -ne 0 ]] ; then
        echo "lock failed, exiting"
        exit
    fi
    
    #Now we are inside the "critical section"
    echo "inside lock"
    sleep 5
    exec 9>&- #close fd 9, and release lock
    
    #The part below is outside the critical section (the lock)
    echo "lock released"
    sleep 5
    
    0 讨论(0)
  • 2020-11-22 03:21

    All approaches that test the existence of "lock files" are flawed.

    Why? Because there is no way to check whether a file exists and create it in a single atomic action. Because of this; there is a race condition that WILL make your attempts at mutual exclusion break.

    Instead, you need to use mkdir. mkdir creates a directory if it doesn't exist yet, and if it does, it sets an exit code. More importantly, it does all this in a single atomic action making it perfect for this scenario.

    if ! mkdir /tmp/myscript.lock 2>/dev/null; then
        echo "Myscript is already running." >&2
        exit 1
    fi
    

    For all details, see the excellent BashFAQ: http://mywiki.wooledge.org/BashFAQ/045

    If you want to take care of stale locks, fuser(1) comes in handy. The only downside here is that the operation takes about a second, so it isn't instant.

    Here's a function I wrote once that solves the problem using fuser:

    #       mutex file
    #
    # Open a mutual exclusion lock on the file, unless another process already owns one.
    #
    # If the file is already locked by another process, the operation fails.
    # This function defines a lock on a file as having a file descriptor open to the file.
    # This function uses FD 9 to open a lock on the file.  To release the lock, close FD 9:
    # exec 9>&-
    #
    mutex() {
        local file=$1 pid pids 
    
        exec 9>>"$file"
        { pids=$(fuser -f "$file"); } 2>&- 9>&- 
        for pid in $pids; do
            [[ $pid = $$ ]] && continue
    
            exec 9>&- 
            return 1 # Locked by a pid.
        done 
    }
    

    You can use it in a script like so:

    mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }
    

    If you don't care about portability (these solutions should work on pretty much any UNIX box), Linux' fuser(1) offers some additional options and there is also flock(1).

    0 讨论(0)
提交回复
热议问题