Быстрый и грязный способ убедиться, что одновременно работает только один экземпляр сценария оболочки - PullRequest
166 голосов
/ 09 октября 2008

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

Ответы [ 39 ]

2 голосов
/ 01 июня 2017

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

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

При использовании блокировок в сценариях оболочки я обычно помещаю функцию 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 в любой точке выхода в вашем скрипте.

Я использовал вышеупомянутое в среде Linux и FreeBSD.

1 голос
/ 14 января 2011

Я считаю, что решение bmdhack является наиболее практичным, по крайней мере, для моего случая использования. Использование flock и lockfile основывается на удалении lockfile с использованием rm при завершении скрипта, что не всегда может быть гарантировано (например, 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}

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

1 голос
/ 09 октября 2008

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

1 голос
/ 23 января 2019

Использование блокировки процесса намного сильнее и заботится о неблагодарных выходах также. 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 
1 голос
/ 18 марта 2015

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

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

1 голос
/ 19 апреля 2018

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

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.

1 голос
/ 30 ноября 2017

Пример с flock (1), но без подоболочки. Файл flock () ed / 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
0 голосов
/ 04 октября 2008

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

0 голосов
/ 16 мая 2018

Это я не нашел нигде упомянутого, он использует чтение, я точно не знаю, является ли чтение атомарным, но оно мне до сих пор хорошо ..., это сочно, потому что это только встроенные bash, это В процессе реализации вы запускаете процесс обработки блокировщика и используете его ввод / вывод для управления блокировками, то же самое можно сделать в межпроцессном режиме, просто переключив целевой ввод / вывод из дескрипторов файлов блокировщика в дескриптор файловой системы (exec 3<>/file && exec 4</file)

## gives locks
locker() {
    locked=false
    while read l; do
        case "$l" in
            lock)
                if $locked; then
                    echo false
                else
                    locked=true
                    echo true
                fi
                ;;
            unlock)
                if $locked; then
                    locked=false
                    echo true
                else
                    echo false
                fi
                ;;
            *)
                echo false
                ;;
        esac
    done
}
## locks
lock() {
    local response
    echo lock >&${locker[1]}
    read -ru ${locker[0]} response
    $response && return 0 || return 1
}

## unlocks
unlock() {
    local response
    echo unlock >&${locker[1]}
    read -ru ${locker[0]} response
    $response && return 0 || return 1
}
0 голосов
/ 17 марта 2018

Этот однострочный ответ получен от кого-то связанного Спросите Ubuntu Q & A :

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
#     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.
...