Каков наилучший способ обеспечить выполнение только одного экземпляра сценария 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]
) overlockfile
(от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 `" 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-дисков и диски# 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...
более длинный пример. Этот пример будет ждать глобальные.замок но тайм-аут после слишком долго.
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
репост отсюда от @Barry Kelly .
Я не уверен, что есть однолинейное надежное решение, так что вы можете в конечном итоге свернуть свой собственный.
Lockfiles несовершенны, но меньше, чем при использовании конвейеров "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 on
ps
выход успешно. ПС-grep может быть использован, чтобы убедиться, что файл блокировки уже запущенных процессов, связанных с ним. Возможно, вы могли бы назвать свои lockfiles каким-то образом, чтобы включить информацию о процессе: пользователей, пид и т. д., который может быть использован более поздним вызовом скрипта, чтобы решить, является ли процесс что созданный файл блокировки по-прежнему вокруг.
первый пример теста
[[ $(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 редактирует свой файл отображения, такой как ".файл.СВП".
- игнорировать pids, разветвленные текущими запущенными сценариями оболочки, которые большинство производных команд " grep не могу этого достичь. Используйте команду" pstree-pH pidnum", чтобы просмотреть сведения о текущем состоянии разветвления процесса.
дистрибутивы Ubuntu/Debian имеют
start-stop-daemon
инструмент, который предназначен для той же цели, которую вы описываете. Смотрите также / etc / init.д/скелетон чтобы увидеть, как он используется при написании сценариев запуска/остановки.Ноя
Я бы также рекомендовал посмотреть на 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}