Виконайте завдання cron, лише якщо воно вже не працює


136

Тож я намагаюся створити роботу з крон як свого роду сторожового для демона, який я створив. Якщо демон демонструє помилки та виходить з ладу, я хочу, щоб робота 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
не дуже безпечне рішення, але що робити, якщо є інший процес, який відповідає пошуку, який ви здійснили в грепі? Відповідь rsanden запобігає подібній проблемі за допомогою pidfile.
Еліас Дорнелес

12
Це колесо вже було винайдено десь інше :) Наприклад, serverfault.com/a/82863/108394
Filipe Correia

5
Замість grep -v grep | grep doctype.phpвас можна зробити grep [d]octype.php.
AlexT

Зауважте, що немає необхідності в тому, &якщо це cron запускає скрипт.
lainatnavi

124

Використовуйте 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
Jess

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, можливо, набагато безпечніше, ніж привітання для запущеної програми з такою ж назвою.
Еліас Дорнелес

/ шлях / до / myprogram &> $ HOME / tmp / myprogram.log & ?????? ти мав на увазі / шлях / до / мій програмі >> $ HOME / tmp / myprogram.log &
matteo

1
Чи не слід видаляти файл, коли сценарій закінчується? Або я пропускаю щось дуже очевидне?
Hamzahfrq

1
@matteo: Так, ти маєш рацію. Я це зафіксував у своїх замітках років тому, але забув оновити його тут. Гірше, що я пропустив це і у вашому коментарі, помічаючи лише " >" проти " >>". Вибач за це.
rsanden

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

35

Це дивно, що ніхто не згадував про біг-один . Я вирішив із цим свою проблему.

 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

9

Ви також можете це зробити як однолінійний прямо в своєму кронтаб:

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

5
не дуже безпечно, що робити, якщо є інші команди, які відповідають пошуку grep?
Еліас Дорнелес

1
Це також може бути записане як * * * * * [ ps -ef|grep [c]ommand-eq 0] && <command>, де загортання першої літери вашої команди в дужки виключає її з результатів grep.
Jim Clouse

Мені довелося вживати наступний синтаксис:[ "$(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
трійка

(Крім того, якщо ви дійсно хотіли насправді порахувати кількість відповідних рядків, це 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

Як відповідь до відповіді Earlz, вам потрібен скрипт для обгортки, який створює файл $ PID.running під час його запуску та видалення, коли він закінчується. Скрипт обгортки викликає сценарій, який ви хочете запустити. Обгортка необхідна, якщо цільовий скрипт виходить з ладу або помилки, pid-файл видаляється ..


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


3

Я рекомендую використовувати існуючий інструмент, такий як monit , він буде контролювати та автоматично перезапускати процеси. Більше інформації можна отримати тут . Він повинен бути легко доступним у більшості дистрибутивів.


Кожна відповідь, окрім цієї, відповідає на поверхневе запитання: "Як моя робота з Cron може переконатися, що вона працює лише в одному екземплярі?" коли справжнє запитання - "Як я можу підтримувати процес, що працює перед перезавантаженням?", а правильна відповідь - це не використовувати cron, а замість цього керівника процесів, як monit. Інші параметри включають runit , s6 або, якщо ваш дистрибутив вже використовує systemd, просто створивши службу systemd для процесу, який потрібно підтримувати живим.
клак

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}

робота з крон :

* * * * * /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

Це дозволяє уникнути можливих помилкових збігів (і накладних помилок), і це пригнічує вихід і покладається лише на статус виходу PS.


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

1

Для досягнення цього достатньо простого користувальницького php. Не потрібно плутати сценарій оболонки.

дозволяє припустити, що ви хочете запустити php /home/mypath/example.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);
    }
?>

Потім у свій крон додайте наступне

* * * * * 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.