Каков наилучший способ гарантировать, что запущен только один экземпляр скрипта Bash?[дубликат]

StackOverflow https://stackoverflow.com/questions/1715137

Вопрос

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

В данный момент я делаю:

ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh

но у этого есть несколько проблем:

  1. это выводит проверку за пределы скрипта
  2. это не позволяет мне запускать один и тот же скрипт из разных учетных записей, чего мне иногда хотелось бы.
  3. -C проверяет только первые 14 символов имени процесса

Конечно, я могу написать свою собственную обработку pid-файлов, но я чувствую, что должен быть простой способ сделать это.

Это было полезно?

Решение

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

В качестве примера:

[Terminal #1] $ lockfile -r 0 /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] lockfile: Sorry, giving up on "/tmp/the.lock"

[Terminal #1] $ rm -f /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] $ 

После /tmp/the.lock после приобретения ваш скрипт будет единственным, имеющим доступ к исполнению.Когда вы закончите, просто снимите блокировку.В виде скрипта это может выглядеть следующим образом:

#!/bin/bash

lockfile -r 0 /tmp/the.lock || exit 1

# Do stuff here

rm -f /tmp/the.lock

Другие советы

Консультативная блокировка использовалась целую вечность, и ее можно использовать в скриптах bash.Я предпочитаю простой flock (из util-linux[-ng]) над lockfile (из procmail).И всегда помните о ловушке при выходе (sigspec == EXIT или 0, перехват определенных сигналов является излишним) в этих сценариях.

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

Вот упомянутый шаблон для вашего удобства.

#!/bin/bash
# SPDX-License-Identifier: MIT

## Copyright (C) 2009 Przemyslaw Pawelczyk <przemoc@gmail.com>
##
## This script is licensed under the terms of the MIT license.
## https://opensource.org/licenses/MIT
#
# Lockable script boilerplate

### HEADER ###

LOCKFILE="/var/lock/`basename $0`"
LOCKFD=99

# PRIVATE
_lock()             { flock -$1 $LOCKFD; }
_no_more_locking()  { _lock u; _lock xn && rm -f $LOCKFILE; }
_prepare_locking()  { eval "exec $LOCKFD>\"$LOCKFILE\""; trap _no_more_locking EXIT; }

# ON START
_prepare_locking

# PUBLIC
exlock_now()        { _lock xn; }  # obtain an exclusive lock immediately or fail
exlock()            { _lock x; }   # obtain an exclusive lock
shlock()            { _lock s; }   # obtain a shared lock
unlock()            { _lock u; }   # drop a lock

### BEGIN OF SCRIPT ###

# Simplest example is avoiding running multiple instances of script.
exlock_now || exit 1

# Remember! Lock file is removed when one of the scripts exits and it is
#           the only script holding the lock or lock is not acquired at all.

Я думаю, что flock вероятно, это самый простой (и запоминающийся) вариант.Я использую его в задании cron для автоматического кодирования DVD и компакт - диски

# try to run a command, but fail immediately if it's already running
flock -n /var/lock/myjob.lock   my_bash_command

Использование -w для тайм-аутов или опустите опции, чтобы дождаться снятия блокировки.Наконец, на странице руководства показан хороший пример для нескольких команд:

   (
     flock -n 9 || exit 1
     # ... commands executed under lock ...
   ) 9>/var/lock/mylockfile

Используйте set -o noclobber выберите и попытайтесь перезаписать общий файл.

Краткий пример

if ! (set -o noclobber ; echo > /tmp/global.lock) ; then
    exit 1  # the global.lock already exists
fi

# ... remainder of script ...

Более длинный пример

Этот пример будет ждать, пока global.lock файл, но время ожидания слишком долгое.

 function lockfile_waithold()
 {
    declare -ir time_beg=$(date '+%s')
    declare -ir time_max=7140  # 7140 s = 1 hour 59 min.

    # poll for lock file up to ${time_max}s
    # put debugging info in lock file in case of issues ...
    while ! \
       (set -o noclobber ; \
        echo -e "DATE:$(date)\nUSER:$(whoami)\nPID:$$" > /tmp/global.lock \ 
       ) 2>/dev/null
    do
        if [ $(($(date '+%s') - ${time_beg})) -gt ${time_max} ] ; then
            echo "Error: waited too long for lock file /tmp/global.lock" 1>&2
            return 1
        fi
        sleep 1
    done

    return 0
 }

 function lockfile_release()
 {
    rm -f /tmp/global.lock
 }

 if ! lockfile_waithold ; then
      exit 1
 fi
 trap lockfile_release EXIT

 # ... remainder of script ...


(Это похоже на этот пост автор: @Barry Kelly, который был замечен позже.)

Я не уверен, что существует какое-либо однострочное надежное решение, так что в конечном итоге вы можете создать свое собственное.

Файлы блокировки несовершенны, но в меньшей степени, чем при использовании конвейеров 'ps | grep | grep -v'.

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

. my_script_control.ksh

# Function exits if cannot start due to lockfile or prior running instance.
my_start_me_up lockfile_name;
trap "rm -f $lockfile_name; exit" 0 2 3 15

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

Использование отдельного управляющего скрипта означает, что вы можете проверять работоспособность на наличие пограничных случаев:удалите устаревшие файлы журнала, убедитесь, что файл блокировки правильно связан с запущенным в данный момент экземпляром скрипта, укажите опцию завершения запущенного процесса и так далее.Это также означает, что у вас больше шансов использовать grep на ps вывод выполнен успешно.ps-grep можно использовать для проверки того, что с файлом блокировки связан запущенный процесс.Возможно, вы могли бы каким-то образом назвать свои файлы блокировки, чтобы включить информацию о процессе:user, pid и т.д., Которые могут быть использованы при последующем вызове скрипта, чтобы решить, существует ли процесс, создавший файл блокировки. который все еще существует.

первый тестовый пример

[[ $(lsof -t $0| wc -l) > 1 ]] && echo "At least one of $0 is running"

второй тестовый пример

currsh=$0
currpid=$$
runpid=$(lsof -t $currsh| paste -s -d " ")
if [[ $runpid == $currpid ]]
then
  sleep 11111111111111111
else
  echo -e "\nPID($runpid)($currpid) ::: At least one of \"$currsh\" is running !!!\n"
  false
  exit 1
fi

объяснение

"lsof -t", чтобы перечислить все pid текущих запущенных скриптов с именем "$ 0".

Команда "lsof" даст два преимущества.

  1. Игнорируйте pids, которые редактируются таким редактором, как vim, потому что vim редактирует свой файл сопоставления, такой как ".file.swp".
  2. Игнорируйте pids, разветвленные текущими запущенными сценариями оболочки, которые большинство производных команд "grep" не могут этого достичь.Используйте команду "pstree -pH pidnum", чтобы просмотреть подробную информацию о текущем состоянии разветвления процесса.

я нашел это в зависимостях пакетов procmail:

apt install liblockfile-bin

Бежать:dotlockfile -l file.lock

файл.будет создана блокировка.

Чтобы разблокировать:dotlockfile -u file.lock

Используйте это, чтобы перечислить файлы этого пакета / команду: dpkg-query -L liblockfile-bin

Дистрибутивы Ubuntu /Debian имеют start-stop-daemon инструмент, предназначенный для той же цели, которую вы описываете.Смотрите также /etc/init.d/скелет чтобы увидеть, как это используется при написании сценариев запуска / остановки.

-- Ной

Я бы также рекомендовал посмотреть на чпст (часть runit):

chpst -L /tmp/your-lockfile.loc ./script.name.sh

Универсальное решение в одну линию:

[ "$(pgrep -fn $0)" -ne "$(pgrep -fo $0)" ] && echo "At least 2 copies of $0 are running"

У меня была такая же проблема, и я придумал шаблон который использует lockfile, pid-файл, содержащий идентификационный номер процесса, и kill -0 $(cat $pid_file) убедитесь, что прерванные сценарии не останавливают следующий запуск.Это создает папку foobar-$USERID в /tmp, в которой находятся lockfile и pid-файл.

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

#!/bin/bash

user_id_num=$(id -u)
pid_file="/tmp/foobar-$user_id_num/foobar-$user_id_num.pid"
lock_file="/tmp/foobar-$user_id_num/running.lock"
ps_id=$$

function alertRunningPS () {
    local PID=$(cat "$pid_file" 2> /dev/null)
    echo "Lockfile present. ps id file: $PID"
    echo "Checking if process is actually running or something left over from crash..."
    if kill -0 $PID 2> /dev/null; then
        echo "Already running, exiting"
        exit 1
    else
        echo "Not running, removing lock and continuing"
        rm -f "$lock_file"
        lockfile -r 0 "$lock_file"
    fi
}

echo "Hello, checking some stuff before locking stuff"

# Lock further operations to one process
mkdir -p /tmp/foobar-$user_id_num
lockfile -r 0 "$lock_file" || alertRunningPS

# Do stuff here
echo -n $ps_id > "$pid_file"
echo "Running stuff in ONE ps"

sleep 30s

rm -f "$lock_file"
rm -f "$pid_file"
exit 0

Я нашел довольно простой способ обработки "одной копии скрипта на систему".Однако это не позволяет мне запускать несколько копий скрипта из многих учетных записей (то есть в стандартном Linux).

Решение:

В начале сценария я дал:

pidof -s -o '%PPID' -x $( basename $0 ) > /dev/null 2>&1 && exit

По - видимому пидоф отлично работает таким образом, что:

  • у него нет ограничений на имя программы, например ps -C ...
  • это не требует от меня делать grep -v grep ( или что - нибудь подобное )

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

Что касается проверки с помощью "одной копии скрипта на запущенного пользователя", я написал это, но я не слишком доволен этим:

(
    pidof -s -o '%PPID' -x $( basename $0 ) | tr ' ' '\n'
    ps xo pid= | tr -cd '[0-9\n]'
) | sort | uniq -d

и затем я проверяю его вывод - если он пуст - нет копий скрипта от того же пользователя.

из с вашим сценарием:

ps -ef | grep $0 | grep $(whoami)

Вот наш стандартный фрагмент.Он может восстановиться после того, как скрипт каким-то образом умер, не очищая его файл блокировки.

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

# lock to ensure we don't get two copies of the same job
script_name="myscript.sh"
lock="/var/run/${script_name}.pid"
if [[ -e "${lock}" ]]; then
    pid=$(cat ${lock})
    if [[ -e /proc/${pid} ]]; then
        echo "${script_name}: Process ${pid} is still running, exiting."
        exit 1
    else
        # Clean up previous lock file
        rm -f ${lock}
   fi
fi
trap "rm -f ${lock}; exit $?" INT TERM EXIT
# write $$ (PID) to the lock file
echo "$$" > ${lock}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top