Корректные сценарии оболочки привязки?

Использование GNU screen Ваш лучший выбор.

Выполнение экрана "Пуск", когда Вы сначала входите в систему - я работаю screen -D -R, выполните свою команду, и или разъедините или приостановите ее с CTRL-Z и затем разъединение с экрана путем нажатия CTRL-A затем D.

Когда Вы входите в машину снова, снова соединяетесь путем выполнения screen -D -R. Вы будете в той же оболочке как прежде. Можно работать jobs видеть приостановленный процесс, если Вы сделали так и работали %1 (или соответствующее задание #) к переднему плану это снова.

66
08.05.2016, 00:17
13 ответов

Вот другой способ сделать привязывающийся сценарий оболочки, который может предотвратить состояние состязания, которое Вы описываете выше, куда два задания могут оба передать строку 3. noclobber опция будет работать в ksh и ударе. Не использовать set noclobber потому что Вы не должны писать сценарий в csh/tcsh.;)

lockfile=/var/tmp/mylock

if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null; then

        trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT

        # do stuff here

        # clean up after yourself, and release your trap
        rm -f "$lockfile"
        trap - INT TERM EXIT
else
        echo "Lock Exists: $lockfile owned by $(cat $lockfile)"
fi

YMMV с соединением NFS (Вы знаете, когда серверы NFS не достижимы), но в целом это намного более устойчиво, чем это раньше было. (10 лет назад)

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

У меня нет опыта с lockrun, но наличие предварительно установленной среды блокировки до сценария, на самом деле работающего, могло бы помочь. Или это не могло бы. Вы просто устанавливаете тест для lockfile вне Вашего сценария в обертке, и теоретически, Вы не могли только поразить то же состояние состязания, если бы два задания назвал lockrun в точно то же время, так же, как с решением 'внутреннего сценария'?

Захват файла является в значительной степени поведением системы чести так или иначе, и любые сценарии, которые не проверяют на существование lockfile до выполнения, сделают то, что они собираются сделать. Только путем включения теста lockfile и правильного поведения, Вы будете решать 99% потенциальных проблем, если не 100%.

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


РЕДАКТИРОВАНИЕ НИЖЕ - 06.05.2016 (если Вы используете KSH88),


Основа на комментарии @Clint Pachl ниже при использовании ksh88 использует mkdir вместо noclobber. Это главным образом смягчает потенциальное состояние состязания, но не полностью ограничивает его (хотя риск миниатюрен). Для получения дополнительной информации читайте ссылку, которую Clint отправил ниже.

lockdir=/var/tmp/mylock
pidfile=/var/tmp/mylock/pid

if ( mkdir ${lockdir} ) 2> /dev/null; then
        echo $$ > $pidfile
        trap 'rm -rf "$lockdir"; exit $?' INT TERM EXIT
        # do stuff here

        # clean up after yourself, and release your trap
        rm -rf "$lockdir"
        trap - INT TERM EXIT
else
        echo "Lock Exists: $lockdir owned by $(cat $pidfile)"
fi

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

Для более современного удара noclobber метод наверху должен подойти.

45
27.01.2020, 19:32
  • 1
    нет, с lockrun, у Вас нет проблемы - когда сервер NFS зависает, все вызовы lockrun, зависнут (по крайней мере), в lockf() системный вызов - когда это вернулось все процессы, возобновляется, но только один процесс выиграет блокировку. Никакое состояние состязания. Я не сталкиваюсь с такими проблемами с cronjobs много - противоположное имеет место - но это - проблема, когда это поражает Вас, это имеет потенциал для создания большого количества боли. –  maxschlepzig 04.10.2011, 23:15
  • 2
    я принял этот ответ, потому что метод надежен и до сих пор самый изящный. Я предлагаю маленький вариант: set -o noclobber && echo "$$" > "$lockfile" получить безопасную нейтрализацию, когда оболочка не поддерживает noclobber опцию. –  maxschlepzig 07.10.2011, 12:45
  • 3
    Хороший ответ, но необходимо также 'уничтожить-0' значение в lockfile, чтобы гарантировать, что процесс, который создал блокировку все еще, существует. –  Nigel Horne 17.12.2014, 15:29
  • 4
    noclobber опция может быть подвержена условиям состязания. См. mywiki.wooledge.org/BashFAQ/045 для некоторой пищи для размышления. –  Clint Pachl 16.06.2015, 08:09
  • 5
    : использование noclobber (или -C) в ksh88 не работает, потому что ksh88 не использует O_EXCL для noclobber. Если Вы работаете с более новой оболочкой, можно быть в порядке... –  jrw32982 supports Monica 17.11.2015, 21:24

Не используйте файл.

Если Ваш сценарий выполняется как это, например:

bash my_script

Можно обнаружить, если это выполняет использование:

running_proc=$(ps -C bash -o pid=,cmd= | grep my_script);
if [[ "$running_proc" != "$$ bash my_script" ]]; do 
  echo Already locked
  exit 6
fi
1
27.01.2020, 19:32
  • 1
    Гм, PS, проверяющий код, работает из my_script? В случае другой экземпляр работает - не делает running_proc содержать два согласующих отрезка длинной линии? Мне нравится идея, но конечно - Вы получите ложные результаты, когда другой пользователь запустит скрипт с тем же именем... спасибо –  maxschlepzig 05.10.2011, 11:20
  • 2
    Это также включает состояние состязания: если 2 экземпляра выполняют первую строку параллельно затем, ни один не получает 'блокировку' и оба выхода с состоянием 6. Это было бы своего рода одним круглым взаимным исчерпанием ресурсов. Btw, я не уверен, почему Вы используете $! вместо $$ в Вашем примере. –  maxschlepzig 05.10.2011, 15:02
  • 3
    @maxschlepzig действительно извините о неправильном $! по сравнению с $$ –  frogstarr78 05.10.2011, 20:29
  • 4
    @maxschlepzig для обработки многочисленных пользователей, запускающих скрипт, добавляют euser = к-o аргументу. –  frogstarr78 05.10.2011, 20:33
  • 5
    @maxschlepzig для предотвращения нескольких строк можно также изменить аргументы grep или дополнительные "фильтры" (например. grep -v $$). В основном я пытался обеспечить другой подход к проблеме. –  frogstarr78 05.10.2011, 20:39

Я понимаю это mkdir является атомарным, так возможно:

lockdir=/var/tmp/myapp
if mkdir $lockdir; then
  # this is a new instance, store the pid
  echo $$ > $lockdir/PID
else
  echo Job is already running, pid $(<$lockdir/PID) >&2
  exit 6
fi

# then set traps to cleanup upon script termination 
# ref http://www.shelldorado.com/goodcoding/tempfiles.html
trap 'rm -r "$lockdir" >/dev/null 2>&1' 0
trap "exit 2" 1 2 3 13 15
12
27.01.2020, 19:32

Я предпочитаю использовать жесткие ссылки.

lockfile=/var/lock/mylock
tmpfile=${lockfile}.$$
echo $$ > $tmpfile
if ln $tmpfile $lockfile 2>&-; then
    echo locked
else
    echo locked by $(<$lockfile)
    rm $tmpfile
    exit
fi
trap "rm ${tmpfile} ${lockfile}" 0 1 2 3 15
# do what you need to

Жесткие ссылки являются атомарными по NFS и по большей части, mkdir также. Используя mkdir(2) или link(2) о том же, на практическом уровне; я просто предпочитаю использовать жесткие ссылки, потому что больше реализаций NFS позволило атомарные жесткие ссылки, чем атомарный mkdir. С современными выпусками NFS Вам не придется взволновать использование также.

14
27.01.2020, 19:32

Я использую инструмент командной строки "скопление" для управления, привязывает мои сценарии удара, как описано здесь и здесь. Я использовал этот простой метод из страницы справочника скопления, для выполнения некоторых команд в подоболочке...

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

В том примере это перестало работать с кодом выхода 1, если это не может получить lockfile. Но скопление может также использоваться способами, которые не требуют, чтобы команды были выполнены в подоболочке :-)

1
27.01.2020, 19:32
  • 1
    flock() системный вызов не работает по NFS. –  maxschlepzig 05.10.2011, 11:03
  • 2
    BSD имеет подобный инструмент, "lockf". –  dubiousjim 14.11.2012, 16:06
  • 3
    @dubiousjim, BSD lockf также звонит flock() и таким образом проблематично по NFS. Btw, тем временем, скопление () на Linux теперь отступают к fcntl() когда файл расположен на NFS, монтируются, таким образом, в среде NFS только для Linux flock() теперь работает по NFS. –  maxschlepzig 08.05.2016, 00:24

Я использую dtach.

$ dtach -n /tmp/socket long_running_task ; echo $?
0
$ dtach -n /tmp/socket long_running_task ; echo $?
dtach: /tmp/socket: Address already in use
1
2
27.01.2020, 19:32

Я предлагаю следующее решение в сценарии, названном 'flocktest'

#!/bin/bash
export LOGFILE=`basename $0`.logfile
logit () {
echo "$1" >>$LOGFILE
}
PROGPATH=$0
(
flock -x -n 257
(($?)) && logit "'$PROGPATH' is already running!" && exit 0
logit "'$PROGPATH', proc($$): sleeping 30 seconds"
sleep 30
)257<$PROGPATH
-3
27.01.2020, 19:32

Проверьте мой сценарий...

Можно ЛЮБИТЬ его....

[rambabu@Server01 ~]$ sh Prevent_cron-OR-Script_against_parallel_run.sh
Parallel RUN Enabled
Now running
Task completed in Parallel RUN...
[rambabu@Server01 ~]$ cat Prevent_cron-OR-Script_against_parallel_run.sh
#!/bin/bash
#Created by RambabuKella
#Date : 12-12-2013

#LOCK file name
Parallel_RUN="yes"
#Parallel_RUN="no"
PS_GREP=0
LOCK=/var/tmp/mylock_`whoami`_"$0"
#Checking for the process
PS_GREP=`ps -ef |grep "sh $0" |grep -v grep|wc -l`
if [ "$Parallel_RUN" == "no" ] ;then
echo "Parallel RUN Disabled"

 if [ -f $LOCK ] || [ $PS_GREP -gt 2   ] ;then
        echo -e "\nJob is already running OR LOCK file exists. "
        echo -e "\nDetail are : "
        ps -ef |grep  "$0" |grep -v grep
        cat "$LOCK"
  exit 6
 fi
echo -e "LOCK file \" $LOCK \" created on : `date +%F-%H-%M` ." &> $LOCK
# do some work
echo "Now running"
echo "Task completed on with single RUN ..."
#done

rm -v $LOCK 2>/dev/null
exit 0
else

echo "Parallel RUN Enabled"

# do some work
echo "Now running"
echo "Task completed in Parallel RUN..."
#done

exit 0
fi
echo "some thing wrong"
exit 2
[rambabu@Server01 ~]$
-2
27.01.2020, 19:32
[1126810] Простой способ - использовать

lockfile

, обычно поставляемый с пакетом [1127359] procmail[1127360].

8
27.01.2020, 19:32
[112241] Используя инструмент [112552]FLOM (Free LOck Manager)[112553], команды сериализации становятся такими же простыми, как и запуск

FLOM позволяет реализовать более софистичные варианты использования (распределенная блокировка, читатели/писатели, числовые ресурсы и т.д.......................... как описано здесь: [112554]http://sourceforge.net/p/flom/wiki/FLOM%20by%20examples/

0
27.01.2020, 19:32

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

Поместите содержимое ниже, например, в / opt / racechecker / racechecker:

ZPROGRAMNAME=$(readlink -f $0)
EZPROGRAMNAME=`echo $ZPROGRAMNAME | sed 's/\//_/g'`
EZMAIL="/usr/bin/mail"
EZCAT="/bin/cat"

if  [ -n "$EZPROGRAMNAME" ] ;then
        EZPIDFILE=/tmp/$EZPROGRAMNAME.pid
        if [ -e "$EZPIDFILE" ] ;then
                EZPID=$($EZCAT $EZPIDFILE)
                echo "" | $EZMAIL -s "$ZPROGRAMNAME already running with pid $EZPID"  alarms@someemail.com >>/dev/null
                exit -1
        fi
        echo $$ >> $EZPIDFILE
        function finish {
          rm  $EZPIDFILE
        }
        trap finish EXIT
fi

Вот как его использовать. Обратите внимание на строку после shebang:

     #/bin/bash
     . /opt/racechecker/racechecker
     echo "script are running"
     sleep 120

Он работает так, что он определяет имя основного файла bashscript и создает файл pid в каталоге "/ tmp". Он также добавляет слушателя к сигналу финиша. Слушатель удалит файл pid, когда основной скрипт завершится должным образом.

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

Что делать, если сценарий дает сбой

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

В случае сбоя системы

Рекомендуется сохранить файл pidfile / lockfile, например, в каталоге / tmp. Таким образом, ваши сценарии обязательно продолжат выполнение после сбоя системы, поскольку файлы pid всегда будут удаляться при загрузке.

0
27.01.2020, 19:32

sem , который входит в состав инструментов GNU parallel , может быть тем, что вы ищете:

sem [--fg] [--id <id>] [--semaphoretimeout <secs>] [-j <num>] [--wait] command

Как в:

sem --id my_semaphore --fg "echo 1 ; date ; sleep 3" &
sem --id my_semaphore --fg "echo 2 ; date ; sleep 3" &
sem --id my_semaphore --fg "echo 3 ; date ; sleep 3" &

вывод:

1
Thu 10 Nov 00:26:21 UTC 2016
2
Thu 10 Nov 00:26:24 UTC 2016
3
Thu 10 Nov 00:26:28 UTC 2016

Обратите внимание, что order isn ' т гарантировано. Также вывод не отображается, пока он не закончится (раздражает!). Но даже в этом случае это самый краткий из известных мне способов защиты от одновременного выполнения, не беспокоясь о файлах блокировки, повторных попытках и очистке.

5
27.01.2020, 19:32

Для фактического использования вы должны использовать ответ с наибольшим количеством голосов .

Тем не менее, я хочу обсудить некоторые различные сломанные и частично -работоспособные подходы с использованием psи множество предостережений, которые они имеют, поскольку я продолжаю видеть, как люди их используют.

Этот ответ на самом деле является ответом на «Почему бы не использовать psи grepдля обработки блокировки в оболочке?»

Сломанный подход #1

Во-первых, подход, описанный в другом ответе , который имеет несколько положительных голосов, несмотря на то, что он не (и никогда не мог )работать и явно никогда не тестировался:

running_proc=$(ps -C bash -o pid=,cmd= | grep my_script);
if [[ "$running_proc" != "$$ bash my_script" ]]; do 
  echo Already locked
  exit 6
fi

Давайте исправим синтаксические ошибки и неработающие psаргументы и получим:

running_proc=$(ps -C bash -o pid,cmd | grep "$0");
echo "$running_proc"
if [[ "$running_proc" != "$$ bash $0" ]]; then
  echo Already locked
  exit 6
fi

Этот сценарий будет всегда выходить из 6, каждый раз, независимо от того, как вы его запускаете.

Если вы запустите его с ./myscript, тогда вывод psбудет просто 12345 -bash, который не соответствует требуемой строке 12345 bash./myscript, поэтому это не удастся.

Если вы запустите его с bash myscript, все станет еще интереснее. Процесс bash разветвляется для запуска конвейера, а дочерняя оболочка запускает psи grep. И исходная оболочка, и дочерняя оболочка будут отображаться в выводе ps, примерно так:

25793 bash myscript
25795 bash myscript

Это не ожидаемый результат $$ bash $0, поэтому ваш скрипт завершится.

Сломанный подход #2

Теперь, отдавая должное пользователю, написавшему неверный подход #1, я сам сделал что-то подобное, когда впервые попробовал это:

if otherpids="$(pgrep -f "$0" | grep -vFx "$$")" ; then
  echo >&2 "There are other copies of the script running; exiting."
  ps >&2 -fq "${otherpids//$'\n'/ }" # -q takes about a tenth the time as -p
  exit 1
fi

Это почти работает. Но тот факт, что труба разветвляется, отбрасывает это. Так что этот тоже всегда будет выходить.

Ненадежный подход #3

pids_this_script="$(pgrep -f "$0")"
if not_this_process="$(echo "$pids_this_script" | grep -vFx "$$")"; then
  echo >&2 "There are other copies of this script running; exiting."
  ps -fq "${not_this_process//$'\n'/ }"
  exit 1
fi

Эта версия позволяет избежать проблемы разветвления конвейера в подходе #2, сначала получая все PID, которые имеют текущий сценарий в своих аргументах командной строки, и , а затем фильтруя этот pidlist отдельно,чтобы опустить PID текущего скрипта.

Это может сработать... при условии, что ни у одного другого процесса нет командной строки, соответствующей $0, и при условии, что сценарий всегда вызывается одинаково (, например. если он вызывается с относительным путем, а затем с абсолютным путем, последний экземпляр не заметит первого ).

Ненадежный подход #4

А что, если мы пропустим проверку всей командной строки, поскольку это может не указывать на то, что скрипт действительно запущен, и вместо этого проверим lsof, чтобы найти все процессы, в которых этот скрипт открыт?

Ну да, такой подход на самом деле не так уж и плох:

if otherpids="$(lsof -t "$0" | grep -vFx "$$")"; then
  echo >&2 "Error: There are other processes that have this script open - most likely other copies of the script running.  Exiting to avoid conflicts."
  ps >&2 -fq "${otherpids//$'\n'/ }"
  exit 1
fi

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

Или если работающий скрипт изменен (, например. с Vim или с git checkout), то «новая» версия скрипта запустится без проблем, так как и Vim, и git checkoutприводят к созданию нового файла (нового индексного дескриптора )вместо Старый.

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

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

Но помните, не используйте этот подход, если сценарий может быть отредактирован или скопирован, так как вы получите ложные отрицательные значения , т.е. одновременное выполнение нескольких экземпляров -, так что тот факт, что редактирование с помощью Vim не не давать ложных срабатываний вроде не должно иметь значения для вас. Я упоминаю об этом, потому что подход #3 действительно дает ложные срабатывания (, т.е.отказывается запускаться ), если у вас открыт скрипт в Vim.

Так что же делать?

лучший -ответ на этот вопрос дает хороший надежный подход.

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

1
27.01.2020, 19:32

Теги

Похожие вопросы