Chỉ chạy công việc định kỳ nếu nó chưa chạy


136

Vì vậy, tôi đang cố gắng thiết lập một công việc định kỳ như một loại cơ quan giám sát cho một daemon mà tôi đã tạo ra. Nếu daemon lỗi và thất bại, tôi muốn công việc định kỳ khởi động lại nó ... Tôi không chắc điều này có thể xảy ra, nhưng tôi đã đọc qua một vài hướng dẫn về cron và không thể tìm thấy bất cứ điều gì sẽ làm những gì tôi làm Tôi đang tìm ...

Trình nền của tôi được bắt đầu từ một tập lệnh shell, vì vậy tôi thực sự chỉ tìm cách để chạy một công việc định kỳ CHỈ nếu lần chạy trước của công việc đó vẫn không chạy.

Tôi đã tìm thấy bài đăng này , nó đã cung cấp một giải pháp cho những gì tôi đang cố gắng sử dụng các tệp khóa, không phải là tôi không chắc có cách nào tốt hơn để làm điều đó không ...

Cảm ơn bạn đã giúp đỡ.

Câu trả lời:


120

Tôi làm điều này cho một chương trình bộ đệm in mà tôi đã viết, nó chỉ là một tập lệnh 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

Nó chạy cứ sau hai phút và khá hiệu quả. Tôi có nó gửi email cho tôi với thông tin đặc biệt nếu vì lý do nào đó quá trình không chạy.


4
Tuy nhiên, không phải là một giải pháp rất an toàn, nếu có quá trình khác phù hợp với tìm kiếm bạn đã làm trong grep thì sao? Câu trả lời của rsanden ngăn chặn loại vấn đề đó bằng cách sử dụng pidfile.
Elias Dornele

12
Bánh xe này đã được phát minh ở một nơi khác :) Ví dụ: serverfault.com/a/82863/108394
Filipe Correia

5
Thay vì grep -v grep | grep doctype.phpbạn có thể làm grep [d]octype.php.
AlexT

Lưu ý rằng không cần thiết &nếu nó là cron chạy tập lệnh.
lainatnavi

124

Sử dụng flock. Nó mới. Thế tốt hơn rồi.

Bây giờ bạn không phải tự viết mã. Kiểm tra thêm lý do tại đây: https://serverfault.com/a/82863

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

1
Một giải pháp rất dễ dàng
MFB

4
Giải pháp tốt nhất, tôi đã sử dụng nó trong một thời gian dài.
soger

1
Tôi cũng đã tạo một mẫu cron đẹp ở đây: gist.github.com/jesslilly/315132a59f749c11b7c6
Jess

3
setlock, s6-setlock, chpst, Và runlocktrong chế độ non-blocking của họ là lựa chọn thay thế có sẵn trên nhiều hơn chỉ là Linux. unix.stackexchange.com/a/475580/5132
JdeBP

3
Tôi cảm thấy đây nên là câu trả lời được chấp nhận. Quá đơn giản!
Codemonkey

62

Như những người khác đã nêu, viết và kiểm tra tệp PID là một giải pháp tốt. Đây là cách thực hiện bash của tôi:

#!/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 Sử dụng pidfile có lẽ an toàn hơn nhiều so với grepping cho một chương trình đang chạy có cùng tên.
Elias Dornele

/ path / to / myprogram &> $ HOME / tmp / myprogram.log & ?????? bạn có lẽ có nghĩa là / đường dẫn / đến / myprogram >> $ HOME / tmp / myprogram.log &
matteo

1
Không nên xóa tập tin khi tập lệnh kết thúc? Hay tôi đang thiếu một cái gì đó rất rõ ràng?
Hamzahfrq

1
@matteo: Vâng, bạn nói đúng. Tôi đã sửa nó trong ghi chú của tôi nhiều năm trước nhưng quên cập nhật nó ở đây. Tệ hơn, tôi cũng đã bỏ lỡ nó trong bình luận của bạn, chỉ để ý " >" so với " >>". Xin lỗi vì điều đó.
rsanden

5
@ Hamzahfrq: Đây là cách nó hoạt động: Trước tiên, kịch bản kiểm tra xem liệu tệp PID có tồn tại không (" [ -e "${PIDFILE}" ]". Nếu không, thì nó sẽ khởi động chương trình ở chế độ nền, ghi PID của nó vào một tệp (" echo $! > "${PIDFILE}"") và thoát. Nếu tập tin PID thay vào đó tồn tại, thì tập lệnh sẽ kiểm tra các quy trình của riêng bạn (" ps -u $(whoami) -opid=") và xem liệu bạn có đang chạy một tập tin có cùng bộ điều khiển PID không (" grep -P "^\s*$(cat ${PIDFILE})$""). Nếu bạn không, thì nó sẽ bắt đầu lập trình như trước, ghi đè tập tin PID bằng bộ mã mới và thoát. Tôi thấy không có lý do gì để sửa đổi tập lệnh, phải không?
rsanden

35

Thật ngạc nhiên khi không ai nhắc đến run-one . Tôi đã giải quyết vấn đề của mình với điều này.

 apt-get install run-one

sau đó thêm run-onetrước tập lệnh crontab của bạn

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

Kiểm tra câu hỏi SE này của Ubuntu. Bạn có thể tìm thấy liên kết đến một thông tin chi tiết ở đó là tốt.


22

Đừng cố gắng làm điều đó thông qua cron. Cho cron chạy tập lệnh bất kể là gì, và sau đó tập lệnh quyết định xem chương trình có chạy hay không và khởi động nó nếu cần thiết (lưu ý bạn có thể sử dụng Ruby hoặc Python hoặc ngôn ngữ kịch bản yêu thích của bạn để làm điều này)


5
Cách cổ điển là đọc một tệp PID mà dịch vụ tạo ra khi nó khởi động, kiểm tra xem quy trình với PID đó có còn chạy không và khởi động lại nếu không.
tvanfosson

9

Bạn cũng có thể làm điều đó như một lớp lót trực tiếp trong crontab của bạn:

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

5
không an toàn lắm, nếu có các lệnh khác phù hợp với tìm kiếm grep thì sao?
Elias Dornele

1
Điều này cũng có thể được viết dưới dạng * * * * * [ ps -ef|grep [c]ommand-eq 0] && <lệnh> trong đó gói chữ cái đầu tiên của lệnh của bạn trong ngoặc loại trừ nó khỏi kết quả grep.
Jim Clouse

Tôi đã phải sử dụng cú pháp sau:[ "$(ps -ef|grep [c]ommand|wc -l)" -eq 0 ] && <command>
thameera

1
Điều này thật gớm ghiếc. [ $(grep something | wc -l) -eq 0 ]là một cách thực sự bùng nổ để viết ! grep -q something. Vì vậy, bạn muốn đơn giảnps -ef | grep '[c]ommand' || command
tripleee

(Ngoài ra, như một bên, nếu bạn thực sự muốn đếm số lượng các dòng phù hợp, đó là grep -c.)
tripleee

7

Cách tôi đang làm khi tôi đang chạy các tập lệnh php là:

Các crontab:

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

Mã php:

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

Lệnh này đang tìm kiếm trong danh sách quy trình hệ thống cho tên tệp php hiện tại nếu nó tồn tại bộ đếm dòng (wc -l) sẽ lớn hơn một vì lệnh tìm kiếm có chứa tên tệp

Vì vậy, nếu bạn chạy php crons, hãy thêm đoạn mã trên vào đầu mã php của bạn và nó sẽ chỉ chạy một lần.


Đây là những gì tôi cần vì tất cả các giải pháp khác yêu cầu cài đặt một cái gì đó trên máy chủ, mà tôi không có quyền truy cập để làm.
Jeff Davis

5

Theo câu trả lời của Earlz, bạn cần một tập lệnh trình bao bọc tạo ra tệp $ PID.rucky khi nó khởi động và xóa khi kết thúc. Kịch bản lệnh bao bọc gọi kịch bản bạn muốn chạy. Trình bao bọc là cần thiết trong trường hợp tập lệnh đích bị lỗi hoặc lỗi, tệp pid bị xóa ..


Ôi tuyệt quá ... Tôi chưa bao giờ nghĩ đến việc sử dụng trình bao bọc ... Tôi không thể tìm ra cách để làm điều đó bằng cách sử dụng các tệp khóa vì tôi không thể đảm bảo rằng tệp sẽ bị xóa nếu trình nền bị lỗi ... A trình bao bọc sẽ hoạt động hoàn hảo, tôi sẽ cung cấp cho giải pháp của jjclarkson, nhưng tôi sẽ làm điều này nếu nó không hoạt động ...
LorenVS


3

Tôi khuyên bạn nên sử dụng một công cụ hiện có như monit , nó sẽ giám sát và tự động khởi động lại các quy trình. Có nhiều thông tin có sẵn ở đây . Nó nên dễ dàng có sẵn trong hầu hết các bản phân phối.


Mỗi câu trả lời ngoại trừ câu trả lời này trả lời câu hỏi bề mặt "Làm thế nào công việc định kỳ của tôi có thể đảm bảo nó chỉ chạy một phiên bản?" khi câu hỏi thực sự là "Làm thế nào tôi có thể duy trì quá trình của mình khi đối mặt với việc khởi động lại?", và câu trả lời đúng thực sự là không sử dụng cron, mà thay vào đó là một người giám sát quá trình như monit. Các tùy chọn khác bao gồm runit , s6 hoặc, nếu bản phân phối của bạn đã sử dụng systemd, chỉ cần tạo một dịch vụ systemd cho quy trình cần được duy trì.
clacke

3

Điều này không bao giờ làm tôi thất bại:

một.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}

công việc định kỳ :

* * * * * /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
}

CẬP NHẬT .. cách tốt hơn bằng cách sử dụng đàn:

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

1

Tôi muốn đề xuất những điều sau đây như là một cải tiến cho câu trả lời của rsanden (Tôi sẽ đăng dưới dạng nhận xét, nhưng không có đủ danh tiếng ...):

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

Điều này tránh các kết quả sai có thể xảy ra (và chi phí chung của grepping), và nó triệt tiêu đầu ra và chỉ dựa vào trạng thái thoát của ps.


1
psLệnh của bạn sẽ khớp với các PID cho những người dùng khác trên hệ thống, không chỉ riêng bạn. Thêm " -u" vào pslệnh sẽ thay đổi cách hoạt động của trạng thái thoát.
rsanden

1

Php tùy chỉnh đơn giản là đủ để đạt được. Không cần nhầm lẫn với shell script.

giả sử bạn muốn chạy php /home/mypath/example.php nếu không chạy

Sau đó sử dụng tập lệnh php tùy chỉnh để thực hiện cùng một công việc.

tạo sau /home/mypath/forver.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);
    }
?>

Sau đó, trong cron của bạn thêm sau

* * * * * php /home/mypath/forever.php 'php /home/mypath/example.php'

0

Cân nhắc sử dụng pgrep (nếu có) thay vì ps chuyển qua grep nếu bạn sẽ đi theo tuyến đường đó. Mặc dù, về mặt cá nhân, tôi đã có rất nhiều dặm trong các kịch bản của mẫu

while(1){
  call script_that_must_run
  sleep 5
}

Mặc dù điều này có thể thất bại và các công việc cron thường là cách tốt nhất cho các công cụ cần thiết. Chỉ là một sự thay thế khác.


2
Điều này sẽ chỉ bắt đầu daemon nhiều lần và không giải quyết vấn đề được đề cập ở trên.
cwoebker

0

Tài liệu: https://www.timkay.com/solo/

solo là một tập lệnh rất đơn giản (10 dòng) ngăn chương trình chạy nhiều bản sao cùng một lúc. Nó rất hữu ích với cron để đảm bảo rằng một công việc không chạy trước khi một công việc trước đó kết thúc.

Thí dụ

* * * * * solo -port=3801 ./job.pl blah blah
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.