เรียกใช้งาน cron เฉพาะในกรณีที่ยังไม่ได้ทำงาน


136

ดังนั้นฉันจึงพยายามตั้งค่างาน cron เป็นสุนัขเฝ้าบ้านสำหรับภูตที่ฉันสร้างขึ้น หาก daemon เกิดข้อผิดพลาดและล้มเหลวฉันต้องการให้งาน cron เริ่มต้นใหม่เป็นระยะ ... ฉันไม่แน่ใจว่าจะเป็นไปได้อย่างไร แต่ฉันอ่านบทแนะนำ cron สองสามข้อและไม่พบสิ่งใดที่จะทำในสิ่งที่ฉัน 'ฉันกำลังมองหา ...

daemon ของฉันเริ่มต้นจากเชลล์สคริปต์ดังนั้นฉันแค่มองหาวิธีเรียกใช้งาน 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
ไม่ใช่วิธีการแก้ปัญหาที่ปลอดภัยมากถ้ามีกระบวนการอื่นที่ตรงกับการค้นหาที่คุณทำใน grep คำตอบของ rsanden ป้องกันปัญหาประเภทนั้นโดยใช้ pidfile
Elias Dorneles

12
วงล้อนี้ถูกประดิษฐ์ขึ้นที่อื่นแล้ว :) เช่นserverfault.com/a/82863/108394
Filipe Correia

5
แทนการที่คุณสามารถทำได้grep -v grep | grep doctype.php grep [d]octype.php
AlexT

โปรดทราบว่าไม่จำเป็น&หาก cron เรียกใช้สคริปต์
lainatnavi

125

ใช้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ในโหมดที่ไม่ปิดกั้นของพวกเขาเป็นทางเลือกที่มีอยู่ในมากกว่าเพียงแค่ลินุกซ์ 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 สำหรับโปรแกรมที่ใช้ชื่อเดียวกัน
Elias Dorneles

/ path / to / myprogram &> $ HOME / tmp / myprogram.log & ?????? คุณอาจจะหมายถึง / path / to / myprogram >> $ 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

คุณยังสามารถทำเป็นซับเดียวได้โดยตรงใน crontab ของคุณ:

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

5
ไม่ค่อยปลอดภัยจะเกิดอะไรขึ้นถ้ามีคำสั่งอื่นที่ตรงกับการค้นหา grep?
Elias Dorneles

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

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) จะมากกว่าหนึ่งเนื่องจากคำสั่งค้นหาเองที่มีชื่อไฟล์

ดังนั้นหากคุณใช้ php crons ให้เพิ่มโค้ดด้านบนที่จุดเริ่มต้นของโค้ด php ของคุณและมันจะทำงานเพียงครั้งเดียว


นี่คือสิ่งที่ฉันต้องการเนื่องจากโซลูชันอื่น ๆ ทั้งหมดจำเป็นต้องติดตั้งบางสิ่งบางอย่างบนเซิร์ฟเวอร์ไคลเอนต์ซึ่งฉันไม่มีสิทธิ์เข้าถึง
Jeff Davis

5

ในการติดตามคำตอบของ Earlz คุณต้องมีสคริปต์ Wrapper ที่สร้างไฟล์ $ PID.running เมื่อเริ่มต้นและลบเมื่อสิ้นสุด สคริปต์ Wrapper เรียกสคริปต์ที่คุณต้องการเรียกใช้ จำเป็นต้องใช้ wrapper ในกรณีที่สคริปต์เป้าหมายล้มเหลวหรือเกิดข้อผิดพลาดไฟล์ pid จะถูกลบ ..


โอ้เจ๋ง ... ฉันไม่เคยคิดเกี่ยวกับการใช้กระดาษห่อ ... ฉันไม่สามารถหาวิธีทำได้โดยใช้ไฟล์ล็อคเพราะฉันไม่สามารถรับประกันได้ว่าไฟล์จะถูกลบหากภูตทำผิดพลาด ... A กระดาษห่อหุ้มจะทำงานได้อย่างสมบูรณ์ฉันจะให้วิธีแก้ปัญหาของ jjclarkson แต่ฉันจะทำเช่นนี้หากไม่ได้ผล ...
LorenVS


3

ผมอยากจะแนะนำให้ใช้เครื่องมือที่มีอยู่เช่นmonitก็จะตรวจสอบและกระบวนการรีสตาร์ทอัตโนมัติ มีข้อมูลเพิ่มเติมสามารถใช้ได้ที่นี่ ควรจะหาได้ง่ายในการแจกแจงส่วนใหญ่


ทุกคำตอบยกเว้นคำตอบนี้จะตอบคำถามพื้นผิว "งาน cron ของฉันจะทำให้แน่ใจได้อย่างไรว่าทำงานเพียงอินสแตนซ์เดียว" เมื่อคำถามที่แท้จริงคือ "ฉันจะทำให้กระบวนการของฉันทำงานต่อไปเมื่อต้องรีสตาร์ทได้อย่างไร" และคำตอบที่ถูกต้องคือไม่ใช้ cron แต่จะเป็นผู้ดูแลกระบวนการเช่นมอนิเตอร์แทน ตัวเลือกอื่น ๆ ได้แก่runit , s6หรือหากการแจกจ่ายของคุณใช้ systemd อยู่แล้วเพียงแค่สร้างบริการ systemd สำหรับกระบวนการที่ต้องมีชีวิตอยู่
clacke

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

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

พิจารณาใช้ pgrep (ถ้ามี) แทน ps piped ผ่าน grep หากคุณจะไปเส้นทางนั้น แม้ว่าโดยส่วนตัวแล้วฉันได้รับไมล์สะสมจากสคริปต์ของแบบฟอร์มมากมาย

while(1){
  call script_that_must_run
  sleep 5
}

แต่นี้สามารถล้มเหลวและงาน cron เป็นมักจะเป็นวิธีที่ดีที่สุดสำหรับสิ่งที่จำเป็น อีกทางเลือกหนึ่ง


2
นี่จะเป็นการเริ่มต้นภูตซ้ำแล้วซ้ำเล่าและไม่สามารถแก้ปัญหาดังกล่าวข้างต้นได้
cwoebker

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.