Führen Sie den Cron-Job nur aus, wenn er noch nicht ausgeführt wird

136

Ich versuche also, einen Cron-Job als eine Art Watchdog für einen von mir erstellten Daemon einzurichten. Wenn der Daemon ausfällt und fehlschlägt, möchte ich, dass der Cron-Job ihn regelmäßig neu startet ... Ich bin mir nicht sicher, wie dies möglich ist, aber ich habe ein paar Cron-Tutorials durchgelesen und konnte nichts finden, was das tun würde, was ich tun würde suche ...

Mein Daemon wird von einem Shell-Skript aus gestartet, daher suche ich wirklich nur nach einer Möglichkeit, einen Cron-Job NUR auszuführen, wenn die vorherige Ausführung dieses Jobs noch nicht ausgeführt wird.

Ich habe diesen Beitrag gefunden , der eine Lösung für das bietet, was ich mit Sperrdateien versuche. Ich bin mir nicht sicher, ob es einen besseren Weg gibt ...

Danke für Ihre Hilfe.

LorenVS
quelle

Antworten:

120

Ich mache das für ein Print-Spooler-Programm, das ich geschrieben habe, es ist nur ein Shell-Skript:

#!/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

Es läuft alle zwei Minuten und ist sehr effektiv. Ich habe eine E-Mail mit speziellen Informationen, wenn der Prozess aus irgendeinem Grund nicht ausgeführt wird.

jjclarkson
quelle
4
Keine sehr sichere Lösung. Was ist, wenn es einen anderen Prozess gibt, der der Suche in grep entspricht? Die Antwort von rsanden verhindert diese Art von Problem mithilfe einer PID-Datei.
Elias Dorneles
12
Dieses Rad wurde bereits woanders erfunden :) ZB serverfault.com/a/82863/108394
Filipe Correia
5
Anstelle von grep -v grep | grep doctype.phpdir kann grep [d]octype.php.
AlexT
Beachten Sie, dass &das Skript nicht ausgeführt werden muss, wenn es cron ist.
Lainatnavi
124

Verwenden Sie flock. Es ist neu. Es ist besser.

Jetzt müssen Sie den Code nicht mehr selbst schreiben. Weitere Gründe finden Sie hier: https://serverfault.com/a/82863

/usr/bin/flock -n /tmp/my.lockfile /usr/local/bin/my_script
Jess
quelle
1
Eine sehr einfache Lösung
MFB
4
Beste Lösung, ich benutze das schon sehr lange.
Soger
1
Ich habe auch eine schöne Cron-Vorlage hier erstellt: gist.github.com/jesslilly/315132a59f749c11b7c6
Jess
3
setlock, s6-setlock, chpst, Und runlockin ihrem nicht-blockierende Modi sind Alternativen , die auf mehr als nur Linux zur Verfügung stehen. unix.stackexchange.com/a/475580/5132
JdeBP
3
Ich denke, dies sollte die akzeptierte Antwort sein. So einfach!
Codemonkey
62

Wie bereits erwähnt, ist das Schreiben und Überprüfen einer PID-Datei eine gute Lösung. Hier ist meine Bash-Implementierung:

#!/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}"
rsanden
quelle
3
+1 Mit einer PID-Datei ist es wahrscheinlich viel sicherer, als nach einem laufenden Programm mit demselben Namen zu suchen.
Elias Dorneles
/ path / to / myprogram &> $ HOME / tmp / myprogram.log & ?????? Meinten Sie vielleicht / path / to / myprogram >> $ HOME / tmp / myprogram.log &
matteo
1
Sollte die Datei nicht entfernt werden, wenn das Skript beendet ist? Oder fehlt mir etwas sehr Offensichtliches?
Hamzahfrq
1
@ Matto: Ja, du hast recht. Ich hatte das vor Jahren in meinen Notizen behoben, aber vergessen, es hier zu aktualisieren. Schlimmer noch, ich habe es auch in Ihrem Kommentar verpasst und nur " >" gegen " >>" bemerkt . Das tut mir leid.
Rsanden
5
@Hamzahfrq: So funktioniert es: Das Skript prüft zunächst, ob die PID-Datei vorhanden ist (" [ -e "${PIDFILE}" ]". Wenn dies nicht der Fall ist, startet es das Programm im Hintergrund, schreibt seine PID in eine Datei (" echo $! > "${PIDFILE}"") und Beenden. Wenn die PID-Datei stattdessen vorhanden ist, überprüft das Skript Ihre eigenen Prozesse (" ps -u $(whoami) -opid=") und prüft, ob Sie einen mit derselben PID (" grep -P "^\s*$(cat ${PIDFILE})$"") ausführen. Wenn dies nicht der Fall ist, wird das Skript gestartet Programm wie zuvor, überschreiben Sie die PID-Datei mit der neuen PID und
beenden
35

Es ist überraschend, dass niemand von Run-One gesprochen hat . Ich habe mein Problem damit gelöst.

 apt-get install run-one

run-oneFügen Sie dann vor Ihrem Crontab-Skript hinzu

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

Schauen Sie sich diese Antwort von askubuntu SE an. Dort finden Sie auch einen Link zu detaillierten Informationen.

Bedi Egilmez
quelle
22

Versuchen Sie nicht, es über Cron zu tun. Lassen Sie cron ein Skript ausführen, egal was passiert, und lassen Sie das Skript dann entscheiden, ob das Programm ausgeführt wird, und starten Sie es gegebenenfalls (beachten Sie, dass Sie dazu Ruby oder Python oder Ihre bevorzugte Skriptsprache verwenden können).

Earlz
quelle
5
Die klassische Methode besteht darin, eine PID-Datei zu lesen, die der Dienst beim Start erstellt, zu überprüfen, ob der Prozess mit dieser PID noch ausgeführt wird, und andernfalls neu zu starten.
Tvanfosson
9

Sie können dies auch als Einzeiler direkt in Ihrem Crontab tun:

* * * * * [ `ps -ef|grep -v grep|grep <command>` -eq 0 ] && <command>
quezacoatl
quelle
5
nicht sehr sicher, was ist, wenn es andere Befehle gibt, die der Suche nach grep entsprechen?
Elias Dorneles
1
Dies kann auch als * * * * * [ ps -ef|grep [c]ommand-eq 0] && <Befehl> geschrieben werden, wobei das Umschließen des ersten Buchstabens Ihres Befehls in Klammern ihn von den Grep-Ergebnissen ausschließt.
Jim Clouse
Ich musste die folgende Syntax verwenden:[ "$(ps -ef|grep [c]ommand|wc -l)" -eq 0 ] && <command>
Thameera
1
Das ist schrecklich. [ $(grep something | wc -l) -eq 0 ]ist ein wirklich umständlicher Weg zu schreiben ! grep -q something. Also willst du einfachps -ef | grep '[c]ommand' || command
Tripleee
( grep -c
Nebenbei bemerkt
7

Die Art und Weise, wie ich es mache, wenn ich PHP-Skripte ausführe, ist:

Die Crontab:

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

Der PHP-Code:

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

Dieser Befehl sucht in der Systemprozessliste nach dem aktuellen PHP-Dateinamen, falls vorhanden. Der Zeilenzähler (wc -l) ist größer als eins, da der Suchbefehl selbst den Dateinamen enthält

Wenn Sie also PHP-Crones ausführen, fügen Sie den obigen Code am Anfang Ihres PHP-Codes hinzu und er wird nur einmal ausgeführt.

Talsibony
quelle
Dies war das, was ich brauchte, da alle anderen Lösungen die Installation von etwas auf dem Client-Server erforderten, auf das ich keinen Zugriff habe.
Jeff Davis
5

Als Antwort auf die Antwort von Earlz benötigen Sie ein Wrapper-Skript, das beim Start eine $ PID.running-Datei erstellt und beim Ende löscht. Das Wrapper-Skript ruft das Skript auf, das Sie ausführen möchten. Der Wrapper ist erforderlich, falls das Zielskript fehlschlägt oder Fehler auftreten. Die PID-Datei wird gelöscht.

Byron Whitlock
quelle
Oh cool ... Ich habe nie darüber nachgedacht, einen Wrapper zu verwenden ... Ich konnte keinen Weg finden, dies mit Sperrdateien zu tun, da ich nicht garantieren konnte, dass die Datei gelöscht wird, wenn der Daemon ausfällt ... A. Wrapper würde perfekt funktionieren, ich werde jjclarksons Lösung ausprobieren, aber ich werde dies tun, wenn das nicht funktioniert ...
LorenVS
3

Ich würde empfehlen, ein vorhandenes Tool wie monit zu verwenden , das Prozesse überwacht und automatisch neu startet. Weitere Informationen finden Sie hier . Es sollte in den meisten Distributionen leicht verfügbar sein.

Zitrax
quelle
Jede Antwort außer dieser beantwortet die oberflächliche Frage "Wie kann mein Cron-Job sicherstellen, dass nur eine Instanz ausgeführt wird?" Wenn die eigentliche Frage lautet: "Wie kann ich meinen Prozess trotz Neustarts am Laufen halten?" und die richtige Antwort lautet, dass Sie nicht cron verwenden, sondern einen Prozess-Supervisor wie monit. Weitere Optionen sind runit , s6 oder, wenn Ihre Distribution bereits systemd verwendet, nur ein systemd-Dienst für den Prozess zu erstellen, der am Leben erhalten werden muss.
Clacke
3

Dieser hat mich nie im Stich gelassen:

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 Job :

* * * * * /path/to/one.sh <command>
DJV
quelle
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
}

UPDATE .. besserer Weg mit Herde:

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

Ich würde Folgendes als Verbesserung von Rsandens Antwort vorschlagen (ich würde als Kommentar posten, aber nicht genug Ruf haben ...):

#!/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

Dies vermeidet mögliche falsche Übereinstimmungen (und den Aufwand für das Greppen), unterdrückt die Ausgabe und hängt nur vom Exit-Status von ps ab.

dbenton
quelle
1
Ihr psBefehl würde PIDs für andere Benutzer im System entsprechen, nicht nur für Ihre eigenen. Durch Hinzufügen eines " -u" zum psBefehl wird die Funktionsweise des Exit-Status geändert.
Rsanden
1

Einfache benutzerdefinierte PHP ist genug zu erreichen. Keine Notwendigkeit, mit Shell-Skript zu verwechseln.

Nehmen wir an, Sie möchten php /home/mypath/example.php ausführen, wenn es nicht ausgeführt wird

Verwenden Sie dann das folgende benutzerdefinierte PHP-Skript, um den gleichen Job auszuführen.

Erstelle folgendes /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);
    }
?>

Fügen Sie dann in Ihrem Cron Folgendes hinzu

* * * * * php /home/mypath/forever.php 'php /home/mypath/example.php'
Lingeshram
quelle
0

Verwenden Sie pgrep (falls verfügbar) anstelle von ps, das durch grep geleitet wird, wenn Sie diesen Weg gehen möchten. Obwohl ich persönlich eine Menge Kilometer mit Skripten des Formulars habe

while(1){
  call script_that_must_run
  sleep 5
}

Dies kann jedoch fehlschlagen und Cron-Jobs sind oft der beste Weg für wichtige Dinge. Nur eine andere Alternative.

Richard Thomas
quelle
2
Dies würde den Daemon nur immer wieder starten und das oben erwähnte Problem nicht lösen.
Cwoebker
0

Dokumente: https://www.timkay.com/solo/

solo ist ein sehr einfaches Skript (10 Zeilen), das verhindert, dass ein Programm mehr als eine Kopie gleichzeitig ausführt. Mit cron ist es hilfreich sicherzustellen, dass ein Job nicht ausgeführt wird, bevor ein vorheriger abgeschlossen wurde.

Beispiel

* * * * * solo -port=3801 ./job.pl blah blah
Erlang P.
quelle