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

2分ごとに実行され、非常に効果的です。何らかの理由でプロセスが実行されていない場合は、特別な情報をメールで送信します。


4
非常に安全な解決策ではありませんが、grepで行った検索と一致する他のプロセスがある場合はどうなりますか?rsandenの答えは、pidfileを使用してそのような問題を防ぎます。
エリアスドルネレス2012年

12
このホイールはすでに別の場所で発明されています:)例:serverfault.com/a/82863/108394
フィリップコレイア

5
代わりにgrep -v grep | grep doctype.phpできますgrep [d]octype.php
AlexT 2016年

&スクリプトを実行するcron である場合、は必要ないことに注意してください。
lainatnavi

124

を使用しflockます。それは新しい。それは良いです。

これで、自分でコードを記述する必要はありません。こちらでその他の理由を確認してください:https//serverfault.com/a/82863

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

1
非常に簡単なソリューション
MFB 2016

4
最良の解決策、私はこれを本当に長い間使用してきました。
soger

1
ここにも素敵なcronテンプレートの要旨を作成しました:gist.github.com/jesslilly/315132a59f749c11b7c6
Jess

3
setlocks6-setlockchpst、及び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するよりもはるかに安全です。
エリアスドルネレス2012年

/ path / to / myprogram&> $ HOME / tmp / myprogram.log&?????? もしかして、/ path / to / myprogram >> $ HOME / tmp / myprogram.log&
matteo

1
スクリプトが終了したときにファイルを削除しないでください。または私は非常に明白な何かを逃していますか?
Hamzahfrq 14

1
@matteo:はい、そうです。私は何年も前にノートでそれを修正しましたが、ここで更新するのを忘れていました。さらに悪いことに、「>」と「>>」だけに気付いて、コメントにも記載していません。申し訳ありません。
rsanden 14

5
@Hamzahfrq:仕組みは次のとおりです。スクリプトは最初にPIDファイルが存在するかどうかを確認します( " [ -e "${PIDFILE}" ]"。存在しない場合は、プログラムをバックグラウンドで起動し、そのPIDをファイルに書き込みます( " echo $! > "${PIDFILE}"")。終了します。代わりに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

9

crontabでワンライナーとして直接実行することもできます。

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

5
あまり安全ではありません。grepの検索に一致する他のコマンドがある場合はどうなりますか?
エリアスドルネレス2012年

1
これは、* * * * * [ ps -ef|grep [c]ommand-eq 0] && <command> としても記述できます。コマンドの最初の文字を角括弧で囲むと、grepの結果から除外されます。
ジムクロース2013

:私は、次の構文を使用する必要がありました[ "$(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 '19

(また、余談ですが、一致する行の数を実際にカウントしたい場合は、それですgrep -c。)
tripleee

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 cronを実行している場合は、上記のコードをphpコードの先頭に追加すると、1回だけ実行されます。


他のすべてのソリューションでは、クライアントサーバーに何かをインストールする必要がありましたが、これにはアクセスできません。
ジェフデイビス

5

Earlz回答のフォローアップとして、開始時に$ PID.runningファイルを作成し、終了時に削除するラッパースクリプトが必要です。ラッパースクリプトは、実行するスクリプトを呼び出します。ラッパーは、ターゲットスクリプトが失敗した場合やエラーが発生した場合に必要です。pidファイルは削除されます。


ラッパーを使用することを考えたことはありません...デーモンがエラーになった場合にファイルが削除されることを保証できなかったため、ロックファイルを使用してそれを行う方法を理解できませんでした... Aラッパーは完全に機能します。jjclarksonのソリューションを試してみますが、機能しない場合はこれを実行します...
LorenVS


3

monitなどの既存のツールを使用することをお勧めします。これは、プロセスを監視して自動再起動します。ここで利用可能な詳細情報があります。ほとんどのディストリビューションで簡単に利用できるはずです。


これ以外のすべての回答は、表面的な質問「私のcronジョブが1つのインスタンスのみを実行していることをどのように確認できますか?」に回答します。実際の質問が「再起動に直面してプロセスを実行し続けるにはどうすればよいですか?」であり、正しい答えは確かにcronではなく、monitのようなプロセス監視プログラムを使用することです。他のオプションにはrunits6、またはディストリビューションがすでにsystemdを使用している場合は、そのまま維持する必要があるプロセス用のsystemdサービスを作成するだけです。
2018

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
}

UPDATE .. 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 14

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

そのルートに行くつもりなら、grepを介してパイプ処理されたpsではなく、pgrep(使用可能な場合)の使用を検討してください。個人的には、私は次の形式のスクリプトから多くのマイレージを得ています

while(1){
  call script_that_must_run
  sleep 5
}

ただし、これは失敗する可能性があり、cronジョブ多くの場合、本質的なもののための最良の方法です。ちょうど別の選択肢。


2
これは単にデーモンを何度も起動するだけで、上記の問題を解決しません。
cwoebker 2013

0

ドキュメント:https : //www.timkay.com/solo/

ソロは、プログラムが一度に複数のコピーを実行するのを防ぐ非常に単純なスクリプト(10行)です。前のジョブが完了する前にジョブが実行されないようにするために、cronを使用すると便利です。

* * * * * solo -port=3801 ./job.pl blah blah
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.