Каков наилучший способ гарантировать, что запущен только один экземпляр скрипта Bash?[дубликат]
Вопрос
На этот вопрос уже есть ответ здесь:
Каков самый простой / наилучший способ обеспечить выполнение только одного экземпляра данного скрипта - при условии, что это Bash в Linux?
В данный момент я делаю:
ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh
но у этого есть несколько проблем:
- это выводит проверку за пределы скрипта
- это не позволяет мне запускать один и тот же скрипт из разных учетных записей, чего мне иногда хотелось бы.
-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" даст два преимущества.
- Игнорируйте pids, которые редактируются таким редактором, как vim, потому что vim редактирует свой файл сопоставления, такой как ".file.swp".
- Игнорируйте 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}