Ejecute cron job solo si aún no se está ejecutando

136

Así que estoy tratando de configurar un trabajo cron como una especie de perro guardián para un demonio que he creado. Si el daemon falla y falla, quiero que el trabajo cron lo reinicie periódicamente ... No estoy seguro de lo posible, pero leí un par de tutoriales cron y no pude encontrar nada que hiciera lo que yo quería. estoy buscando ...

Mi demonio comienza desde un script de shell, por lo que realmente solo estoy buscando una manera de ejecutar un trabajo cron SOLAMENTE si la ejecución anterior de ese trabajo aún no se está ejecutando.

Encontré esta publicación , que proporcionó una solución para lo que estoy tratando de hacer usando archivos de bloqueo, no estoy seguro de si hay una mejor manera de hacerlo ...

Gracias por tu ayuda.

LorenVS
fuente

Respuestas:

120

Hago esto para un programa de cola de impresión que escribí, es solo un script de shell:

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

Se ejecuta cada dos minutos y es bastante efectivo. Tengo un correo electrónico con información especial si por alguna razón el proceso no se está ejecutando.

jjclarkson
fuente
44
Sin embargo, no es una solución muy segura, ¿qué pasa si hay otro proceso que coincida con la búsqueda que hizo en grep? La respuesta de rsanden previene ese tipo de problema usando un archivo pid.
Elias Dorneles
12
Esta rueda ya se inventó en otro lugar :) Por ejemplo, serverfault.com/a/82863/108394
Filipe Correia
55
En lugar de grep -v grep | grep doctype.phpque puedas hacer grep [d]octype.php.
AlexT
Tenga en cuenta que no es necesario que el &cron ejecute el script.
lainatnavi
124

Uso flock. Es nuevo. Es mejor.

Ahora no tiene que escribir el código usted mismo. Vea más razones aquí: https://serverfault.com/a/82863

/usr/bin/flock -n /tmp/my.lockfile /usr/local/bin/my_script
Encadenar
fuente
1
Una solución muy fácil
MFB
44
La mejor solución, he estado usando esto durante mucho tiempo.
Soger
1
También creé un buen resumen de la plantilla cron aquí: gist.github.com/jesslilly/315132a59f749c11b7c6
Jess
3
setlock, s6-setlock, chpst, Y runlocken sus modos no bloqueantes son alternativas que están disponibles en algo más que Linux. unix.stackexchange.com/a/475580/5132
JdeBP
3
Siento que esta debería ser la respuesta aceptada. ¡Tan sencillo!
Codemonkey
62

Como han dicho otros, escribir y verificar un archivo PID es una buena solución. Aquí está mi implementación de 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}"
rsanden
fuente
3
+1 Usar un archivo pid es probablemente mucho más seguro que buscar un programa en ejecución con el mismo nombre.
Elias Dorneles
/ ruta / a / myprogram &> $ HOME / tmp / myprogram.log & ?????? ¿quiso decir / ruta / a / myprogram >> $ HOME / tmp / myprogram.log &
matteo
1
¿No debería eliminarse el archivo cuando finalice el script? ¿O me estoy perdiendo algo muy obvio?
Hamzahfrq
1
@matteo: Sí, tienes razón. Lo había arreglado en mis notas hace años, pero olvidé actualizarlo aquí. Peor aún, también lo extrañé en tu comentario, notando solo " >" versus " >>". Lo siento por eso.
rsanden
55
@Hamzahfrq: así es como funciona: el script primero verifica si el archivo PID existe (" [ -e "${PIDFILE}" ]". Si no es así, iniciará el programa en segundo plano, escriba su PID en un archivo (" echo $! > "${PIDFILE}"") y Si el archivo PID existe, el script verificará sus propios procesos (" ps -u $(whoami) -opid=") y verá si está ejecutando uno con el mismo PID (" grep -P "^\s*$(cat ${PIDFILE})$""). Si no lo está, iniciará el programa como antes, sobrescribir el archivo PID con el nuevo PID, y la salida no veo ninguna razón para modificar la secuencia de comandos;.? ¿verdad
rsanden
35

Es sorprendente que nadie haya mencionado sobre run-one . He resuelto mi problema con esto.

 apt-get install run-one

luego agregue run-oneantes de su secuencia de comandos crontab

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

Mira esta respuesta de askubuntu SE. Puede encontrar el enlace a una información detallada allí también.

Bedi Egilmez
fuente
22

No intentes hacerlo a través de cron. Haga que cron ejecute un script sin importar qué, y luego haga que el script decida si el programa se está ejecutando y lo inicie si es necesario (tenga en cuenta que puede usar Ruby o Python o su lenguaje de script favorito para hacer esto)

Earlz
fuente
55
La forma clásica es leer un archivo PID que el servicio crea cuando se inicia, verificar si el proceso con ese PID aún se está ejecutando y reiniciar si no.
tvanfosson
9

También puede hacerlo como una línea directamente en su crontab:

* * * * * [ `ps -ef|grep -v grep|grep <command>` -eq 0 ] && <command>
quezacoatl
fuente
55
no es muy seguro, ¿qué pasa si hay otros comandos que coinciden con la búsqueda de grep?
Elias Dorneles
1
Esto también podría escribirse como * * * * * [ ps -ef|grep [c]ommand-eq 0] && <comando> donde envolver la primera letra de su comando entre paréntesis lo excluye de los resultados grep.
Jim Clouse
Tuve que usar la siguiente sintaxis:[ "$(ps -ef|grep [c]ommand|wc -l)" -eq 0 ] && <command>
thameera
1
Esto es horrible [ $(grep something | wc -l) -eq 0 ]es una forma realmente indirecta de escribir ! grep -q something. Así que simplemente quieresps -ef | grep '[c]ommand' || command
tripleee
(Además, como grep -c
comentario
7

La forma en que lo hago cuando ejecuto scripts php es:

El crontab:

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

El código php:

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

Este comando está buscando en la lista de procesos del sistema el nombre de archivo php actual si existe, el contador de línea (wc -l) será mayor que uno porque el comando de búsqueda en sí contiene el nombre de archivo

así que si ejecuta php crons, agregue el código anterior al inicio de su código php y se ejecutará solo una vez.

talsibony
fuente
Esto es lo que necesitaba, ya que todas las otras soluciones requerían instalar algo en el servidor del cliente, algo a lo que no tengo acceso.
Jeff Davis
5

Como seguimiento a la respuesta de Earlz, necesita una secuencia de comandos de envoltura que cree un archivo $ PID.running cuando se inicie y se elimine cuando finalice. El script de reiniciador llama al script que desea ejecutar. El contenedor es necesario en caso de que el script de destino falle o se produzca un error, el archivo pid se elimina.

Byron Whitlock
fuente
Oh, genial ... Nunca pensé en usar un contenedor ... No pude encontrar una manera de hacerlo usando archivos de bloqueo porque no podía garantizar que el archivo se eliminaría si el demonio se equivocaba ... A wrapper funcionaría perfectamente, voy a darle una oportunidad a la solución de jjclarkson, pero haré esto si eso no funciona ...
LorenVS
3

Recomendaría usar una herramienta existente como monit , monitoreará y reiniciará automáticamente los procesos. Hay más información disponible aquí . Debería estar fácilmente disponible en la mayoría de las distribuciones.

Zitrax
fuente
Cada respuesta, excepto esta, responde a la pregunta de superficie "¿Cómo puede mi trabajo cron asegurarse de que solo se ejecute una instancia?" cuando la verdadera pregunta es "¿Cómo puedo mantener mi proceso ejecutándose ante los reinicios?", y la respuesta correcta es no usar cron, sino un supervisor de proceso como monit. Otras opciones incluyen runit , s6 o, si su distribución ya usa systemd, simplemente crear un servicio systemd para el proceso que necesita mantenerse vivo.
clacke
3

Este nunca me falló:

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}

trabajo cron :

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

ACTUALIZACIÓN ... mejor forma de usar flock:

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

Sugeriría lo siguiente como una mejora a la respuesta de rsanden (publicaría como un comentario, pero no tengo suficiente reputación ...):

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

Esto evita posibles falsas coincidencias (y la sobrecarga de grepping), y suprime la salida y se basa solo en el estado de salida de ps.

dbenton
fuente
1
Su pscomando coincidiría con los PID de otros usuarios en el sistema, no solo el suyo. Agregar un " -u" al pscomando cambia la forma en que funciona el estado de salida.
rsanden
1

PHP personalizado simple es suficiente para lograr. No es necesario confundirlo con el script de shell.

supongamos que desea ejecutar php /home/mypath/example.php si no se está ejecutando

Luego use el siguiente script php personalizado para hacer el mismo trabajo.

crear siguiente /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);
    }
?>

Luego, en su cron, agregue lo siguiente

* * * * * php /home/mypath/forever.php 'php /home/mypath/example.php'
lingeshram
fuente
0

Considere usar pgrep (si está disponible) en lugar de ps canalizado a través de grep si va a seguir esa ruta. Aunque, personalmente, tengo mucho kilometraje de los scripts de la forma

while(1){
  call script_that_must_run
  sleep 5
}

Aunque esto puede fallar y los trabajos cron son a menudo la mejor manera de hacer cosas esenciales. Solo otra alternativa.

Richard Thomas
fuente
2
Esto simplemente iniciaría el demonio una y otra vez y no resolverá el problema mencionado anteriormente.
cwoebker
0

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

solo es un script muy simple (10 líneas) que evita que un programa ejecute más de una copia a la vez. Con cron es útil asegurarse de que un trabajo no se ejecute antes de que finalice uno anterior.

Ejemplo

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