Быстрый и грязный способ обеспечения только одного экземпляра оболочки script выполняется одновременно

Какой быстрый и грязный способ убедиться, что в данный момент работает только один экземпляр оболочки script?

Ответ 1

Здесь реализована реализация, которая использует файл блокировки и перекликается с PID. Это служит защитой, если процесс уничтожается перед удалением pidfile:

LOCKFILE=/tmp/lock.txt
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
    echo "already running"
    exit
fi

# make sure the lockfile is removed when we exit and then claim it
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}

# do stuff
sleep 1000

rm -f ${LOCKFILE}

Трюк здесь kill -0, который не передает никакого сигнала, а просто проверяет, существует ли процесс с данным PID. Также вызов trap гарантирует, что файл блокировки будет удален, даже когда ваш процесс будет убит (кроме kill -9).

Ответ 2

Используйте flock(1), чтобы сделать исключаемую блокировку блокировки файловым дескриптором. Таким образом, вы можете даже синхронизировать разные части script.

#!/bin/bash

(
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200 || exit 1

  # Do stuff

) 200>/var/lock/.myscript.exclusivelock

Это гарантирует, что код между ( и ) запускается только одним процессом за раз и что процесс не слишком долго ждет блокировки.

Предостережение: эта конкретная команда является частью util-linux. Если вы запустите операционную систему, отличную от Linux, она может быть или не быть доступна.

Ответ 3

Все подходы, которые проверяют существование "файлов блокировки", являются ошибочными.

Почему? Потому что нет способа проверить, существует ли файл и создать его в одном атомном действии. Из-за этого; есть условие гонки, в котором WILL делает попытки взаимного исключения.

Вместо этого вам нужно использовать mkdir. mkdir создает каталог, если он еще не существует, и если это так, он устанавливает код выхода. Что еще более важно, он делает все это в одном атомном действии, что делает его идеальным для этого сценария.

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

Для всех деталей см. отличный BashFAQ: http://mywiki.wooledge.org/BashFAQ/045

Если вы хотите позаботиться об устаревших замках, fuser (1) пригодится. Единственным недостатком здесь является то, что операция занимает около секунды, поэтому это не мгновенно.

Здесь функция, которую я написал однажды, решает проблему с помощью фьюзера:

#       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 
}

Вы можете использовать его в script так:

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

Если вы не заботитесь о переносимости (эти решения должны работать практически во всех UNIX-блоках), Linux fuser (1) предлагает некоторые дополнительные опции, а также flock (1).

Ответ 4

Там обертка вокруг системного вызова стада (2) называется, невообразимо, стайкой (1). Это позволяет легко надежно получить эксклюзивные блокировки, не беспокоясь об очистке и т.д. На странице справочная страница приводятся примеры того, как использовать ее в shell script.

Ответ 5

Вам нужна атомная операция, такая как стая, иначе это в конечном итоге потерпит неудачу.

Но что делать, если стая недоступна. Ну есть mkdir. Это тоже атомная операция. Только один процесс приведет к успешному mkdir, все остальные потерпят неудачу.

Итак, код:

if mkdir /var/lock/.myscript.exclusivelock
then
  # do stuff
  :
  rmdir /var/lock/.myscript.exclusivelock
fi

Вам нужно позаботиться об устаревших блокировках еще раз, после чего ваш script больше никогда не запустится.

Ответ 6

Чтобы сделать блокировку надежной, вам нужна атомная операция. Многие из вышеуказанных предложений не являются атомарными. Предлагаемая утилита lockfile (1) выглядит многообещающей как man-страница что его "устойчивый к NFS". Если ваша ОС не поддерживает файл блокировки (1) и ваше решение должно работать на NFS, у вас мало вариантов.

NFSv2 имеет две атомные операции:

  • символическая
  • переименуйте

С NFSv3 вызов create также является атомарным.

Операции с каталогом НЕ являются атомами под NFSv2 и NFSv3 (см. книгу "NFS Illustrated" Брент Каллаган, ISBN 0-201-32570-5; Брент - ветеран NFS на Солнце).

Зная это, вы можете реализовать спин-блокировки для файлов и каталогов (в оболочке, а не PHP):

блокировать текущий каталог:

while ! ln -s . lock; do :; done

заблокировать файл:

while ! ln -s ${f} ${f}.lock; do :; done

разблокировать текущий каталог (предположение, выполняемый процесс действительно приобрел блокировку):

mv lock deleteme && rm deleteme

разблокировать файл (предположение, выполняемый процесс действительно получил блокировку):

mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme

Удалить также не является атомарным, поэтому сначала переименование (которое является атомарным), а затем удаление.

Для вызовов symlink и rename оба имени файла должны находиться в одной и той же файловой системе. Мое предложение: используйте только простые имена файлов (без путей) и помещайте файл и блокируйте его в тот же каталог.

Ответ 7

Другой вариант - использовать параметр shell noclobber, запустив set -C. Тогда > завершится с ошибкой, если файл уже существует.

Вкратце:

set -C
lockfile="/tmp/locktest.lock"
if echo "$$" > "$lockfile"; then
    echo "Successfully acquired lock"
    # do work
    rm "$lockfile"    # XXX or via trap - see below
else
    echo "Cannot acquire lock - already locked by $(cat "$lockfile")"
fi

Это вызывает вызов оболочки:

open(pathname, O_CREAT|O_EXCL)

который автоматически создает файл или сбой, если файл уже существует.


Согласно комментарию BashFAQ 045, это может завершиться неудачно в ksh88, но оно работает во всех моих оболочках:

$ strace -e trace=creat,open -f /bin/bash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/zsh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/pdksh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/dash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

Интересно, что pdksh добавляет флаг O_TRUNC, но, очевидно, он избыточный:
либо вы создаете пустой файл, либо ничего не делаете.


Как вы выполняете rm, зависит от того, как вы хотите обрабатывать нечистые выходы.

Удалить на чистый выход

Новые прогоны завершаются до тех пор, пока проблема, вызвавшая устранение нечистого выхода, не будет удалена вручную, а файл блокировки будет удален вручную.

# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"

Удалить при любом выходе

Выполняются новые прогоны, если script еще не запущен.

trap 'rm "$lockfile"' EXIT

Ответ 8

Вы можете использовать GNU Parallel для этого, поскольку он работает как мьютекс, когда sem как sem. Итак, в конкретных выражениях вы можете использовать:

sem --id SCRIPTSINGLETON yourScript

Если вам нужен тайм-аут, используйте:

sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript

Тайм-аут <0 означает выход без запуска скрипта, если семафор не выпущен в течение таймаута, тайм-аут> 0 означает запуск сценария в любом случае.

Обратите внимание, что вы должны --id ему имя (с --id), иначе он по умолчанию будет управляющим терминалом.

GNU Parallel - очень простая установка на большинстве платформ Linux/OSX/Unix - это просто скрипт Perl.

Ответ 9

Для сценариев оболочки я склоняюсь к mkdir более flock, поскольку блокировки более переносимы.

В любом случае использование set -e недостаточно. Это только выходит из script, если какая-либо команда выходит из строя. Ваши блокировки будут по-прежнему оставлены.

Для правильной очистки блокировки вам действительно нужно настроить свои ловушки на что-то вроде этого psuedo-кода (снятого, упрощенного и непроверенного, но из активно используемых скриптов):

#=======================================================================
# 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 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

Вот что будет. Все ловушки будут выдавать выход, поэтому функция __sig_exit всегда будет (за исключением SIGKILL), которая очистит ваши блокировки.

Примечание: мои значения выхода не являются низкими значениями. Зачем? Различные системы пакетной обработки производят или ожидают числа от 0 до 31. Применяя их к чему-то еще, я могу настроить, чтобы мои сценарии и пакетные потоки реагировали соответственно на предыдущее пакетное задание или script.

Ответ 10

Действительно быстрый и действительно грязный? Этот однострочный вкладыш в верхней части вашего script будет работать:

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

Конечно, просто убедитесь, что ваше имя script уникально.:)

Ответ 11

Создайте файл блокировки в известном месте и проверьте наличие на script start? Включение PID в файл может оказаться полезным, если кто-то пытается отследить ошибочный экземпляр, который предотвращает выполнение script.

Ответ 12

Этот пример объясняется в man flock, но ему нужны некоторые изменения, потому что мы должны управлять ошибками и кодами выхода:

   #!/bin/bash
   #set -e this is useful only for very stupid scripts because script fails when anything command exits with status more than 0 !! without possibility for capture exit codes. not all commands exits >0 are failed.

( #start subprocess
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200
  if [ "$?" != "0" ]; then echo Cannot lock!; exit 1; fi
  echo $$>>/var/lock/.myscript.exclusivelock #for backward lockdir compatibility, notice this command is executed AFTER command bottom  ) 200>/var/lock/.myscript.exclusivelock.
  # Do stuff
  # you can properly manage exit codes with multiple command and process algorithm.
  # I suggest throw this all to external procedure than can properly handle exit X commands

) 200>/var/lock/.myscript.exclusivelock   #exit subprocess

FLOCKEXIT=$?  #save exitcode status
    #do some finish commands

exit $FLOCKEXIT   #return properly exitcode, may be usefull inside external scripts

Вы можете использовать другой метод, список процессов, которые я использовал в прошлом. Но это более сложный метод выше. Вы должны перечислить процессы с помощью ps, фильтровать по его имени, дополнительный фильтр grep -v grep для удаления паразита и, наконец, считать его grep -c. и сравнить с числом. Его сложная и неопределенная

Ответ 13

Здесь используется подход, который объединяет блокировку атомарного каталога с проверкой на наличие устаревших блокировок через PID и перезапуск, если они устарели. Кроме того, это не зависит от каких-либо базизмов.

#!/bin/dash

SCRIPTNAME=$(basename $0)
LOCKDIR="/var/lock/${SCRIPTNAME}"
PIDFILE="${LOCKDIR}/pid"

if ! mkdir $LOCKDIR 2>/dev/null
then
    # lock failed, but check for stale one by checking if the PID is really existing
    PID=$(cat $PIDFILE)
    if ! kill -0 $PID 2>/dev/null
    then
       echo "Removing stale lock of nonexistent PID ${PID}" >&2
       rm -rf $LOCKDIR
       echo "Restarting myself (${SCRIPTNAME})" >&2
       exec "$0" "[email protected]"
    fi
    echo "$SCRIPTNAME is already running, bailing out" >&2
    exit 1
else
    # lock successfully acquired, save PID
    echo $$ > $PIDFILE
fi

trap "rm -rf ${LOCKDIR}" QUIT INT TERM EXIT


echo hello

sleep 30s

echo bye

Ответ 14

При таргетинге на машину Debian я считаю, что пакет lockfile-progs является хорошим решением. procmail также поставляется с инструментом lockfile. Однако иногда я не зацикливаюсь ни на одном из них.

Здесь мое решение, которое использует mkdir для атомарности и PID файла для обнаружения устаревших блокировок. Этот код в настоящее время работает на установке Cygwin и хорошо работает.

Чтобы использовать его, просто вызовите exclusive_lock_require, когда вам нужно получить эксклюзивный доступ к чему-либо. Дополнительный параметр имени блокировки позволяет совместно использовать блокировки между различными сценариями. Там также две функции нижнего уровня (exclusive_lock_try и exclusive_lock_retry), если вам нужно что-то более сложное.

function exclusive_lock_try() # [lockname]
{

    local LOCK_NAME="${1:-`basename $0`}"

    LOCK_DIR="/tmp/.${LOCK_NAME}.lock"
    local LOCK_PID_FILE="${LOCK_DIR}/${LOCK_NAME}.pid"

    if [ -e "$LOCK_DIR" ]
    then
        local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
        if [ ! -z "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2> /dev/null
        then
            # locked by non-dead process
            echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
            return 1
        else
            # orphaned lock, take it over
            ( echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null && local LOCK_PID="$$"
        fi
    fi
    if [ "`trap -p EXIT`" != "" ]
    then
        # already have an EXIT trap
        echo "Cannot get lock, already have an EXIT trap"
        return 1
    fi
    if [ "$LOCK_PID" != "$$" ] &&
        ! ( umask 077 && mkdir "$LOCK_DIR" && umask 177 && echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null
    then
        local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
        # unable to acquire lock, new process got in first
        echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
        return 1
    fi
    trap "/bin/rm -rf \"$LOCK_DIR\"; exit;" EXIT

    return 0 # got lock

}

function exclusive_lock_retry() # [lockname] [retries] [delay]
{

    local LOCK_NAME="$1"
    local MAX_TRIES="${2:-5}"
    local DELAY="${3:-2}"

    local TRIES=0
    local LOCK_RETVAL

    while [ "$TRIES" -lt "$MAX_TRIES" ]
    do

        if [ "$TRIES" -gt 0 ]
        then
            sleep "$DELAY"
        fi
        local TRIES=$(( $TRIES + 1 ))

        if [ "$TRIES" -lt "$MAX_TRIES" ]
        then
            exclusive_lock_try "$LOCK_NAME" > /dev/null
        else
            exclusive_lock_try "$LOCK_NAME"
        fi
        LOCK_RETVAL="${PIPESTATUS[0]}"

        if [ "$LOCK_RETVAL" -eq 0 ]
        then
            return 0
        fi

    done

    return "$LOCK_RETVAL"

}

function exclusive_lock_require() # [lockname] [retries] [delay]
{
    if ! exclusive_lock_retry "[email protected]"
    then
        exit 1
    fi
}

Ответ 15

Если ограничения стека, которые уже были описаны в других разделах этого потока, не являются проблемой для вас, тогда это должно работать:

#!/bin/bash

{
    # exit if we are unable to obtain a lock; this would happen if 
    # the script is already running elsewhere
    # note: -x (exclusive) is the default
    flock -n 100 || exit

    # put commands to run here
    sleep 100
} 100>/tmp/myjob.lock 

Ответ 16

Некоторые unixes имеют lockfile, который очень похож на уже упомянутый flock.

Из man-страницы:

lockfile можно использовать для создания или больше файлов семафора. Если блокировка- файл не может создать все указанные файлов (в указанном порядке), это ждет сдержанность (по умолчанию 8) секунд и повторяет последний файл, который не удалось. Вы можете указать количество попыток делать до отказ возвращается. Если число повторений - -1 (по умолчанию, т.е. -r-1) lockfile будет повторять попытку навсегда.

Ответ 17

Я хотел покончить с lockfiles, lockdirs, специальными программами блокировки и даже pidof, так как он не найден во всех установках Linux. Также хотел иметь самый простой код (или, по крайней мере, как можно меньше строк). Простейший if оператор в одной строке:

if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi

Ответ 18

На самом деле, хотя ответ bmdhacks почти хорош, есть вероятность, что второй script запускается после того, как он сначала проверит файл lock и до того, как он его написал. Поэтому они оба напишу файл блокировки, и оба они будут работать. Вот как это можно сделать наверняка:

lockfile=/var/lock/myscript.lock

if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null ; then
  trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
else
  # or you can decide to skip the "else" part if you want
  echo "Another instance is already running!"
fi

Параметр noclobber гарантирует, что команда перенаправления не удастся, если файл уже существует. Таким образом, команда перенаправления на самом деле атомарна - вы пишете и проверяете файл с помощью одной команды. Вам не нужно удалять файл блокировки в конце файла - он будет удален ловушкой. Надеюсь, это поможет людям, которые прочитают его позже.

P.S. Я не видел, что Микель правильно ответил на вопрос, хотя он не включил команду trap, чтобы уменьшить вероятность того, что файл блокировки останется после остановки script с помощью Ctrl-C, например. Итак, это полное решение

Ответ 19

Я использую простой подход, который обрабатывает устаревшие файлы блокировки.

Обратите внимание, что некоторые из вышеперечисленных решений, которые хранят pid, игнорируют тот факт, что pid может обернуться. Итак, просто проверка наличия допустимого процесса с сохраненным pid недостаточно, особенно для длинных сценариев.

Я использую noclobber, чтобы убедиться, что только один script может открывать и записывать в файл блокировки за один раз. Кроме того, я храню достаточно информации, чтобы однозначно идентифицировать процесс в файле блокировки. Я определяю набор данных, чтобы однозначно идентифицировать процесс pid, ppid, lstart.

Когда запускается новый script, если он не может создать файл блокировки, он затем проверяет, что процесс, создавший файл блокировки, все еще существует. Если нет, мы предполагаем, что первоначальный процесс умер безжалостной смертью и оставил устаревший файл блокировки. Новый script затем берет на себя ответственность за файл блокировки, и все снова становится миром.

Должно работать с несколькими оболочками на нескольких платформах. Быстрая, портативная и простая.

#!/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

Ответ 20

Добавьте эту строку в начале вашего скрипта

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "[email protected]" || :

Это шаблонный код от человеческой паствы.

Если вы хотите больше регистрации, используйте этот

[ "${FLOCKER}" != "$0" ] && { echo "Trying to start build from queue... "; exec bash -c "FLOCKER='$0' flock -E $E_LOCKED -en '$0' '$0' '[email protected]' || if [ \"\$?\" -eq $E_LOCKED ]; then echo 'Locked.'; fi"; } || echo "Lock is free. Completing."

Это устанавливает и проверяет блокировки с помощью утилиты flock. Этот код определяет, был ли он запущен впервые, проверяя переменную FLOCKER, если для него не задано имя сценария, затем он пытается рекурсивно запустить сценарий снова с использованием flock и с инициализированной переменной FLOCKER, если FLOCKER установлен правильно, а затем выполнить flock на предыдущей итерации. удалось, и это нормально, чтобы продолжить. Если блокировка занята, она терпит неудачу с настраиваемым кодом выхода.

Кажется, он не работает на Debian 7, но, похоже, снова работает с экспериментальным пакетом util-linux 2.25. Он пишет "flock:... Text file busy". Это можно изменить, отключив разрешение на запись в вашем скрипте.

Ответ 21

В существующих ответах либо полагается утилита CLI flock, либо не защищает файл блокировки должным образом. Утилита flock недоступна для всех не-Linux-систем (то есть FreeBSD) и не работает должным образом в NFS.

В первые дни системного администрирования и разработки системы мне сказали, что безопасным и относительно переносимым способом создания файла блокировки является создание временного файла с использованием mkemp(3) или mkemp(1), запись идентифицирующей информации в temp файл (т.е. PID), а затем жестко связать файл temp с файлом блокировки. Если ссылка прошла успешно, то вы успешно получили блокировку.

При использовании блокировок в сценариях оболочки я обычно размещаю функцию obtain_lock() в общем профиле и затем отправляю ее из скриптов. Ниже приведен пример моей функции блокировки:

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;
};

Ниже приведен пример использования функции блокировки:

#!/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

Не забудьте вызвать clean_up в любой точке выхода в script.

Я использовал это выше в среде Linux и FreeBSD.

Ответ 22

PID и lockfiles определенно являются самыми надежными. Когда вы пытаетесь запустить программу, она может проверить файл блокировки, который, и если он существует, он может использовать ps, чтобы узнать, все еще запущен процесс. Если это не так, можно запустить script, обновив PID в файле блокировки самостоятельно.

Ответ 23

Я нахожу, что решение bmdhack является наиболее практичным, по крайней мере для моего варианта использования. Использование flock и lockfile основывается на удалении файла блокировки с использованием rm, когда завершается script, что не всегда может быть гарантировано (например, kill -9).

Я бы изменил одну незначительную вещь в решении bmdhack: он делает вывод об удалении файла блокировки, не заявляя, что это не нужно для безопасной работы этого семафора. Его использование kill -0 гарантирует, что старый файл блокировки для мертвого процесса просто будет проигнорирован/переписан.

Мое упрощенное решение состоит в том, чтобы просто добавить следующее в верхнюю часть вашего синглета:

## Test the lock
LOCKFILE=/tmp/singleton.lock 
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
    echo "Script already running. bye!"
    exit 
fi

## Set the lock 
echo $$ > ${LOCKFILE}

Конечно, этот script по-прежнему имеет недостаток, что процессы, которые, вероятно, начнут в то же время, будут иметь расовую опасность, поскольку проверка блокировки и операции набора не являются одним действием атома. Но предлагаемое решение для этого с помощью lhunath для использования mkdir имеет недостаток, что убитый script может покинуть каталог, тем самым предотвращая запуск других экземпляров.

Ответ 24

semaphoric использует flock (как обсуждалось выше, например, presto8) для реализации подсчет семафора. Он позволяет использовать любое определенное количество параллельных процессов. Мы используем его для ограничения уровня concurrency различных рабочих процессов очереди.

Это как sem, но гораздо более легкий. (Полное раскрытие: я написал это после того, как семя было слишком тяжелым для наших нужд, и не было простой утилиты для подсчета семафора.)

Ответ 25

Пример со стадом (1), но без подоболочки. flock() ed file/tmp/foo никогда не удаляется, но это не имеет значения, поскольку он получает flock() и 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

Ответ 26

Ответил уже миллион раз, но по-другому, без необходимости внешних зависимостей:

LOCK_FILE="/var/lock/$(basename "$0").pid"
trap "rm -f ${LOCK_FILE}; exit" INT TERM EXIT
if [[ -f $LOCK_FILE && -d /proc/'cat $LOCK_FILE' ]]; then
   // Process already exists
   exit 1
fi
echo $$ > $LOCK_FILE

Каждый раз, когда он записывает текущий PID ($$) в файл блокировки и при запуске скрипта проверяет, запущен ли процесс с последним PID.

Ответ 27

Использование блокировки процесса намного сильнее и также заботится о неблаговидных выходах. lock_file остается открытым до тех пор, пока процесс запущен. Он будет закрыт (оболочкой), как только процесс будет существовать (даже если его убьют). Я нашел это очень эффективным:

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 

Ответ 28

Путь стада - путь. Подумайте, что произойдет, когда script внезапно умрет. В стаде вы просто теряете стадо, но это не проблема. Также обратите внимание, что злой трюк состоит в том, чтобы взять стадо самой script.. но это, конечно же, позволяет вам запускать полный вход в проблемы разрешения.

Ответ 29

Быстро и грязно?

#!/bin/sh

if [ -f sometempfile ]
  echo "Already running... will now terminate."
  exit
else
  touch sometempfile
fi

..do what you want here..

rm sometempfile

Ответ 30

Взгляните на FLOM (Free LOck Manager) http://sourceforge.net/projects/flom/: вы можете синхронизировать команды и/или скрипты с помощью абстрактных ресурсов, которые не необходимо заблокировать файлы в файловой системе. Вы можете синхронизировать команды, запущенные в разных системах, без NAS (Network Attached Storage), например сервера NFS (Network File System).

Используя простейший вариант использования, сериализация "command1" и "command2" может быть такой же простой, как выполнение:

flom -- command1

и

flom -- command2

из двух разных сценариев оболочки.