Каков наилучший способ обеспечить запуск только одного экземпляра сценария Bash? [дубликат]
этот вопрос уже есть ответ здесь:
каков самый простой / лучший способ обеспечить запуск только одного экземпляра данного скрипта-при условии, что это Bash на Linux?
на данный момент я делать:
ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh
но есть несколько проблем:
- это ставит проверку вне сценария
- это не позволяет мне запускать один и тот же скрипт из разных учетных записей, которые я хотел бы иногда.
- 
-Cпроверяет только первые 14 символов имени процесса
конечно, я могу написать свою собственную обработку pidfile, но я чувствую, что должен быть простой способ сделать это.
14 ответов
если сценарий одинаков для всех пользователей, вы можете использовать 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]) over lockfile (от procmail). И всегда помните о ловушке на выходе (sigspec == EXIT или 0, захват конкретных сигналов является излишним) в этих сценариях.
в 2009 году я выпустил свой блокируемый скрипт boilerplate (первоначально доступный на моей странице wiki, в настоящее время доступен как суть). Преобразование этого в один экземпляр для каждого пользователя тривиальный. С его помощью вы также можете легко писать сценарии для других сценариев, требующих блокировки или синхронизации.
вот упомянутый шаблон для вашего удобства.
#!/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 `"
LOCKFD=99
# PRIVATE
_lock()             { flock - $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-дисков и cds
# try to run a command, but fail immediately if it's already running
flock -n /var/lock/myjob.lock   my_bash_command
использовать -w для тайм-аутов или оставить варианты, чтобы ждать, пока блокировка не будет освобождена.  Наконец, на странице man показан хороший пример для нескольких команд:
   (
     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...
более длинный пример. Этот пример будет ждать глобальные.lock но тайм-аут после слишком долгого времени.
 function lockfile_waithold()
 {
    declare -ir time_beg=$(date '+%s')
    declare -ir maxtime=7140  # 7140 s = 1 hour 59 min.
    # waiting up to ${maxtime}s for /tmp/global.lock ...
    while ! \
       (set -o noclobber ; \
        echo -e "DATE:$(date)\nUSER:$(whoami)\nPID:$$" > /tmp/global.lock \ 
       ) 2>/dev/null
    do
        if [ $(( $(date '+%s') - ${time_beg})) -gt ${maxtime} ] ; then
            echo "waited too long for /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
 # ...remainder of script
 lockfile_release
Repost отсюда @Barry Kelly .
Я не уверен, что есть однолинейное надежное решение, так что ты можешь в конечном итоге свернуть свой собственный.
Lockfiles несовершенны, но менее, чем использование трубопроводов "ps | grep | grep-v".
сказав это, вы можете рассмотреть вопрос о сохранении управления процессом отдельно от вашего скрипта-есть скрипт start. Или, по крайней мере, разложить его на функции, хранящиеся в отдельном файле, таким образом, вы можете в сценарии вызывающего абонента иметь:
. 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
в каждом скрипте, который нуждается в элементе управления логика. The ловушка гарантирует, что файл не удаляется, когда абонент выходит, таким образом, вам не нужно кодировать это на каждой точке выхода в скрипте.
использование отдельного сценария управления означает, что вы можете проверить здравомыслие для крайних случаев:
удалите устаревшие файлы журнала, убедитесь, что файл блокировки правильно связан с
текущий запущенный экземпляр скрипта, дает возможность убить запущенный процесс и так далее.
Это также означает, что у вас больше шансов использовать grep на ps выход успешно.
Ps-grep можно использовать для проверки того, что с файлом блокировки связан запущенный процесс.
Возможно, вы могли бы назвать свои lockfiles каким - то образом, чтобы включить информацию о процессе:
потребитель, pid, etc., который может использоваться более поздним вызовом скрипта, чтобы решить, является ли процесс
это создало lockfile все еще вокруг.
первый пример теста
[[ $(lsof -t | wc -l) > 1 ]] && echo "At least one of  is running"
второй пример теста
currsh=
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" сделает два преимущества.
- игнорировать pids, который редактируется редактором, таким как vim, потому что vim редактирует свой файл отображения, такой как ".файл.swp".
- игнорировать pids, разветвленные текущими запущенными сценариями оболочки, которые большинство производных команд "grep не могу этого достичь. Используйте команду" pstree-pH pidnum", чтобы просмотреть сведения о текущем состоянии разветвления процесса.
дистрибутивы Ubuntu/Debian имеют start-stop-daemon инструмент, который предназначен для той же цели, которую вы описываете. См. также  / etc / init.д/скелетон чтобы увидеть, как он используется при написании сценариев запуска/остановки.
-- Noah
Я бы также рекомендовал посмотреть на chpst (часть Рунит):
chpst -L /tmp/your-lockfile.loc ./script.name.sh
одна линия окончательное решение:
[ "$(pgrep -fn )" -ne "$(pgrep -fo )" ] && echo "At least 2 copies of  are running"
Я нашел это в зависимостях пакетов procmail:
apt install liblockfile-bin
запустить:
dotlockfile -l file.lock
для разблокировки:
dotlockfile -u file.lock
используйте это, чтобы перечислить эти файлы / команду пакета:
dpkg-query -L liblockfile-bin
у меня была такая же проблема, и придумал шаблон который использует 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  ) > /dev/null 2>&1 && exit
видимо pidof работает таким образом, что:
- он не имеет ограничения на имя программы, как ps -C ...
- это не требует от меня делать grep -v grep( или что-нибудь подобное )
и он не полагается на lockfiles, что для меня большая победа, потому что ретрансляция на них означает, что вы должны добавить обработку устаревших lockfiles - что не очень сложно, но если этого можно избежать - почему бы и нет?
что касается проверки "одной копии скрипта на одного работающего пользователя", я написал это, но я не слишком доволен этим:
(
    pidof -s -o '%PPID' -x $( basename  ) | tr ' ' '\n'
    ps xo pid= | tr -cd '[0-9\n]'
) | sort | uniq -d
и затем я проверяю его вывод - если он пуст - нет копий скрипта из тот же пользователь.
вот наш стандартный бит. Он может восстановиться из сценария каким-то образом, не очищая его lockfile.
он записывает идентификатор процесса в файл блокировки, если он работает нормально. Если он найдет файл блокировки при запуске, он прочитает идентификатор процесса из файла блокировки и проверит, существует ли этот процесс. Если процесс не существует, он удалит устаревший файл блокировки и продолжит работу. И только если файл блокировки существует, и процесс все еще работает, он выйдет. И это пишет сообщение при выходе.
# 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}
