Запускать задание cron, только если оно еще не запущено


136

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

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

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

Спасибо за вашу помощь.

Ответы:


120

Я делаю это для программы диспетчера очереди печати, которую я написал, это просто сценарий оболочки:

#!/bin/sh
if ps -ef | grep -v grep | grep doctype.php ; then
        exit 0
else
        /home/user/bin/doctype.php >> /home/user/bin/spooler.log &
        #mailing program
        /home/user/bin/simplemail.php "Print spooler was not running...  Restarted." 
        exit 0
fi

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


4
не очень безопасное решение, но что, если есть другой процесс, который соответствует поиску, который вы выполнили в grep? Ответ rsanden предотвращает подобную проблему с использованием pidfile.
Элиас Дорнелес

12
Это колесо уже где-то изобрели :) Например, serverfault.com/a/82863/108394
Filipe Correia

5
Вместо grep -v grep | grep doctype.phpтебя можно сделать grep [d]octype.php.
AlexT

Обратите внимание, что в этом нет необходимости, &если скрипт запускает cron.
lainatnavi

125

Использование flock. Это новое. Лучше.

Теперь вам не нужно писать код самостоятельно. Ознакомьтесь с другими причинами здесь: https://serverfault.com/a/82863

/usr/bin/flock -n /tmp/my.lockfile /usr/local/bin/my_script

1
Очень простое решение
MFB

4
Лучшее решение, я использую его очень давно.
soger

1
Я также создал здесь хороший шаблон cron: gist.github.com/jesslilly/315132a59f749c11b7c6
Джесс

3
setlock, s6-setlock, chpst, И runlockв их неблокирующих режимах альтернативы, которые доступны на более , чем просто Linux. unix.stackexchange.com/a/475580/5132
JdeBP

3
Я считаю, что это должен быть принятый ответ. Так просто!
Codemonkey

62

Как утверждали другие, запись и проверка файла PID - хорошее решение. Вот моя реализация на bash:

#!/bin/bash

mkdir -p "$HOME/tmp"
PIDFILE="$HOME/tmp/myprogram.pid"

if [ -e "${PIDFILE}" ] && (ps -u $(whoami) -opid= |
                           grep -P "^\s*$(cat ${PIDFILE})$" &> /dev/null); then
  echo "Already running."
  exit 99
fi

/path/to/myprogram > $HOME/tmp/myprogram.log &

echo $! > "${PIDFILE}"
chmod 644 "${PIDFILE}"

3
+1 Использование pidfile, вероятно, намного безопаснее, чем grepping для запущенной программы с тем же именем.
Элиас Дорнелес

/ путь / к / myprogram &> $ HOME / tmp / myprogram.log & ?????? Возможно, вы имели в виду / path / to / myprogram >> $ HOME / tmp / myprogram.log &
matteo

1
Не следует ли удалить файл после завершения скрипта? Или мне не хватает чего-то очень очевидного?
Hamzahfrq

1
@matteo: Да, ты прав. Я исправил это в своих заметках много лет назад, но забыл обновить его здесь. Хуже того, я тоже пропустил это в вашем комментарии, заметив только >«против >>». Извини за это.
rsanden

5
@Hamzahfrq: Вот как это работает: сценарий сначала проверяет, существует ли файл PID (" [ -e "${PIDFILE}" ]". Если нет, то он запускает программу в фоновом режиме, записывает свой PID в файл (" echo $! > "${PIDFILE}"") и exit. Если вместо этого PID-файл существует, то сценарий проверит ваши собственные процессы (" ps -u $(whoami) -opid=") и увидит, запущен ли вы один с тем же PID (" grep -P "^\s*$(cat ${PIDFILE})$""). Если нет, то он запустит программа, как и раньше, перезапишите файл PID новым идентификатором PID и выйдите. Я не вижу причин изменять сценарий; не так ли?
rsanden

35

Удивительно, что про run-one никто не упомянул . Я решил с этим свою проблему.

 apt-get install run-one

затем добавьте run-oneперед скриптом crontab

*/20 * * * * * run-one python /script/to/run/awesome.py

Посмотрите этот ответ на askubuntu SE. Вы также можете найти ссылку на подробную информацию.


22

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


5
Классический способ - прочитать файл PID, который служба создает при запуске, проверить, работает ли процесс с этим идентификатором PID, и перезапустить его, если нет.
tvanfosson 02

9

Вы также можете сделать это как однострочник прямо в вашем crontab:

* * * * * [ `ps -ef|grep -v grep|grep <command>` -eq 0 ] && <command>

5
не очень безопасно, что делать, если есть другие команды, которые соответствуют поиску grep?
Элиас Дорнелес

1
Это также можно было бы записать как * * * * * [ ps -ef|grep [c]ommand-eq 0] && <command>, где перенос первой буквы вашей команды в скобки исключает ее из результатов grep.
Джим Клаус

Мне пришлось использовать следующий синтаксис:[ "$(ps -ef|grep [c]ommand|wc -l)" -eq 0 ] && <command>
thameera

1
Это отвратительно [ $(grep something | wc -l) -eq 0 ]это действительно окольный способ писать ! grep -q something. Так что вы хотите простоps -ef | grep '[c]ommand' || command
tripleee

(Кроме того, если вы действительно хотите подсчитать количество совпадающих строк, то это grep -c.)
tripleee

7

Я делаю это, когда запускаю скрипты php:

Кронтаб:

* * * * * php /path/to/php/script.php &

Код php:

<?php
if (shell_exec('ps aux | grep ' . __FILE__ . ' | wc  -l') > 1) {
    exit('already running...');
}
// do stuff

Эта команда ищет в списке системных процессов текущее имя файла php, если оно существует, счетчик строк (wc -l) будет больше единицы, потому что сама команда поиска содержит имя файла

поэтому, если вы запускаете php crons, добавьте указанный выше код в начало вашего php-кода, и он будет запускаться только один раз.


Это то, что мне было нужно, так как все другие решения требовали установки чего-то на клиентском сервере, к чему у меня нет доступа.
Джефф Дэвис

5

В продолжение ответа Эрлза вам понадобится сценарий-оболочка, который при запуске создает файл $ PID.running и удаляет его по окончании. Скрипт-оболочка вызывает скрипт, который вы хотите запустить. Оболочка необходима в случае сбоя или ошибки целевого скрипта, файл pid удаляется.


О, круто ... Я никогда не думал об использовании оболочки ... Я не мог придумать, как это сделать с помощью файлов блокировки, потому что я не мог гарантировать, что файл будет удален, если демон выйдет из строя ... A Обертка будет работать отлично, я собираюсь попробовать решение jjclarkson, но я сделаю это, если это не сработает ...
LorenVS 02


3

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


Каждый ответ, кроме этого, отвечает на поверхностный вопрос: «Как мое задание cron может убедиться, что оно запускает только один экземпляр?» когда реальный вопрос звучит так: «Как я могу сохранить работу моего процесса после перезапуска?», и правильный ответ действительно - не использовать cron, а вместо этого использовать супервизор процесса, такой как monit. Другие варианты включают runit , s6 или, если ваш дистрибутив уже использует systemd, просто создание службы systemd для процесса, который необходимо поддерживать.
clacke

3

Этот меня никогда не подводил:

one.sh :

LFILE=/tmp/one-`echo "$@" | md5sum | cut -d\  -f1`.pid
if [ -e ${LFILE} ] && kill -0 `cat ${LFILE}`; then
   exit
fi

trap "rm -f ${LFILE}; exit" INT TERM EXIT
echo $$ > ${LFILE}

$@

rm -f ${LFILE}

cron работа :

* * * * * /path/to/one.sh <command>

3
# one instance only (works unless your cmd has 'grep' in it)
ALREADY_RUNNING_EXIT_STATUS=0
bn=`basename $0`
proc=`ps -ef | grep -v grep | grep "$bn" | grep -v " $$ "`
[ $? -eq 0 ] && {
    pid=`echo $proc | awk '{print $2}'`
    echo "$bn already running with pid $pid"
    exit $ALREADY_RUNNING_EXIT_STATUS
}

ОБНОВЛЕНИЕ .. лучший способ использовать flock:

/usr/bin/flock -n /tmp/your-app.lock /path/your-app args 

1

Я бы предложил следующее в качестве улучшения ответа rsanden (я бы опубликовал как комментарий, но у меня недостаточно репутации ...):

#!/usr/bin/env bash

PIDFILE="$HOME/tmp/myprogram.pid"

if [ -e "${PIDFILE}" ] && (ps -p $(cat ${PIDFILE}) > /dev/null); then
  echo "Already running."
  exit 99
fi

/path/to/myprogram

Это позволяет избежать возможных ложных совпадений (и накладных расходов на grepping), а также подавляет вывод и полагается только на статус выхода ps.


1
Ваш ps команда будет соответствовать PID для других пользователей в системе, а не только для вашего собственного. Добавление " -u" к psкоманде изменяет способ работы статуса выхода.
rsanden

1

Достаточно простого настраиваемого php. Не нужно путать со сценарием оболочки.

Предположим, вы хотите запустить php /home/mypath/example.php, если он не запущен

Затем используйте следующий настраиваемый скрипт php для выполнения той же работы.

создать следующий /home/mypath/forever.php

<?php
    $cmd = $argv[1];
    $grep = "ps -ef | grep '".$cmd."'";
    exec($grep,$out);
    if(count($out)<5){
        $cmd .= ' > /dev/null 2>/dev/null &';
        exec($cmd,$out);
        print_r($out);
    }
?>

Затем в вашем cron добавьте следующее

* * * * * php /home/mypath/forever.php 'php /home/mypath/example.php'

0

Если вы собираетесь пойти по этому пути, подумайте об использовании pgrep (если доступно), а не ps, передаваемого через grep. Хотя лично у меня много пробега из скриптов вида

while(1){
  call script_that_must_run
  sleep 5
}

Хотя это может дать сбой и хроны рабочих мест являются часто лучшим способом для основных вещей. Еще одна альтернатива.


2
Это просто запустит демон снова и снова и не решит проблему, упомянутую выше.
cwoebker

0

Документы: https://www.timkay.com/solo/

solo - это очень простой сценарий (10 строк), который не позволяет программе запускать более одной копии за раз. С cron полезно убедиться, что задание не запускается до завершения предыдущего.

пример

* * * * * solo -port=3801 ./job.pl blah blah
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.