仅当cron作业尚未运行时才运行


136

因此,我试图将cron作业设置为我创建的守护程序的一种看门狗。如果守护程序出错并失败,我希望cron作业定期重新启动它...我不确定这样做的可能性如何,但是我通读了一些cron教程,但找不到任何可以做我的事情正在寻找...

我的守护程序是从Shell脚本开始的,所以我真的只是在寻找一种方法来运行cron作业,除非该作业的前一次运行仍未运行。

我发现了这篇文章,它确实为我试图使用锁定文件提供了解决方案,但我不确定是否有更好的方法来做...

谢谢你的帮助。

Answers:


120

我为我编写的打印后台处理程序执行此操作,这只是一个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

它每两分钟运行一次,非常有效。如果由于某种原因该进程未运行,我会通过电子邮件将特殊信息发送给我。


4
但是,这不是一个非常安全的解决方案,如果还有其他流程与您在grep中进行的搜索匹配,该怎么办?rsanden的答案使用pidfile避免了此类问题。
伊莱亚斯·多内莱斯

12
这个轮子已经在其他地方发明了:)例如,serverfault.com
82863/108394

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 2013年

1
我还在这里创建了一个很好的cron模板要点:gist.github.com/jesslilly/315132a59f749c11b7c6
Jess

3
setlocks6-setlockchpst,并runlock在他们的非阻塞模式的选择上提供的不只是Linux的更多。 unix.stackexchange.com/a/475580/5132
JdeBP '18

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正在运行的同名程序要安全得多。
Elias Dorneles 2012年

/ path / to / myprogram&> $ HOME / tmp / myprogram.log&?????? 您可能是说/ path / to / myprogram >> $ HOME / tmp / myprogram.log&
matteo 2012年

1
脚本完成后是否应该删除文件?还是我遗漏了一些非常明显的东西?
Hamzahfrq 2014年

1
@matteo:是的,你是对的。几年前,我已在笔记中修复了该问题,但忘记在此处进行更新。更糟糕的是,我也没有在您的评论中错过它,只注意到“ >”与“ >>”。对于那个很抱歉。
rsanden 2014年

5
@Hamzahfrq:工作原理如下:该脚本首先检查PID文件是否存在(“ [ -e "${PIDFILE}" ]”。如果不存在,则它将在后台启动程序,将其PID写入文件(“ echo $! > "${PIDFILE}"”),然后如果确实存在PID文件,则该脚本将检查您自己的进程(“ ps -u $(whoami) -opid=”),并查看您是否正在使用相同的PID(“ grep -P "^\s*$(cat ${PIDFILE})$"”)运行该进程。如果不存在,则它将启动该进程。像以前一样编程,用新的PID覆盖PID文件,然后退出,我认为没有理由修改脚本;对吗
rsanden 2014年

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

您也可以直接在crontab中将其作为单行代码执行:

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

5
不是很安全,如果还有其他与搜索grep匹配的命令怎么办?
Elias Dorneles 2012年

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
Tripleee

(此外,如果您确实要实际计算匹配行的数量,那就是grep -c。)
Tripleee 16/09/19

7

当我运行php脚本时,我这样做的方式是:

crontab:

* * * * * 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)会大于1,因为搜索命令本身包含文件名

因此,如果您运行php crons,则将以上代码添加到php代码的开头,它将仅运行一次。


这就是我所需要的,因为所有其他解决方案都需要在客户端服务器上安装一些我无权执行的操作。
杰夫·戴维斯

5

作为Earlz答案的后续措施,您需要一个包装器脚本,该脚本在启动时创建$ PID.running文件,并在结束时删除。包装脚本会调用您希望运行的脚本。如果目标脚本失败或出错,则必须使用包装器,以删除pid文件。


噢,太酷了……我从没考虑过使用包装器……我想不出一种使用锁定文件的方法,因为我不能保证如果守护程序出错了,文件将被删除……包装器可以完美地工作,我将为jjclarkson的解决方案做个尝试,但是如果那不起作用,我会做的……
LorenVS 2010年


3

我建议使用monit之类的现有工具,它将监视并自动重启进程。有更多的可用信息在这里。在大多数发行版中应该都可以轻松获得它。


除此以外的每个答案都回答了表面问题“我的cron工作如何确保它仅运行一个实例?” 当真正的问题是“面对重新启动如何使我的进程继续运行?”时,正确的答案确实是不使用cron,而是使用monit之类的进程管理器。其他选项包括runits6,或者,如果您的发行版已经使用systemd,则仅为需要保持活动状态的进程创建systemd服务。
clacke

3

这个从来没有让我失败:

一个.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
}

更新..使用羊群的更好方法:

/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,而不仅仅是您自己的PID。-ups命令中添加“ ”会更改退出状态的工作方式。
rsanden 2014年

1

简单的自定义php就足以实现。无需与shell脚本混淆。

假设您要运行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(如果可用),而不是通过grep管道传递的ps。不过,就个人而言,我从形式上的脚本中学到了很多东西

while(1){
  call script_that_must_run
  sleep 5
}

虽然这可能会失败,并cron作业经常必不可少的东西的最佳方式。只是另一种选择。


2
这只会一遍又一遍地启动守护程序,而不能解决上述问题。
cwoebker

0

文件:https//www.timkay.com/solo/

solo是一个非常简单的脚本(10行),可防止程序一次运行多个副本。使用cron来确保作业在上一个作业完成之前不会运行是很有用的。

* * * * * solo -port=3801 ./job.pl blah blah
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.