Как заставить скрипт Python запускаться как сервис или демон в Linux - PullRequest
157 голосов
/ 21 октября 2009

Я написал скрипт на Python, который проверяет определенный адрес электронной почты и передает новые сообщения во внешнюю программу. Как я могу заставить этот скрипт выполняться 24/7, например, превращая его в демон или службу в Linux. Нужен ли мне цикл, который никогда не заканчивается в программе, или это можно сделать, просто повторяя код несколько раз?

Ответы [ 13 ]

90 голосов
/ 21 октября 2009

У вас есть два варианта здесь.

  1. Сделайте правильное cron задание , которое вызывает ваш скрипт. Cron - это общее имя для демона GNU / Linux, который периодически запускает сценарии в соответствии с заданным вами расписанием. Вы добавляете свой скрипт в crontab или помещаете символическую ссылку на него в специальный каталог, и демон выполняет его запуск в фоновом режиме. Вы можете читать дальше в Википедии. Существует множество различных демонов cron, но в вашей системе GNU / Linux она должна быть уже установлена.

  2. Используйте своего рода подход python (например, библиотеку), чтобы ваш скрипт мог демонизировать себя. Да, для этого потребуется простой цикл обработки событий (где ваши события запускаются по таймеру, возможно, обеспечивается функцией сна).

Я бы не советовал вам выбирать 2., потому что вы на самом деле повторяете функциональность cron. Системная парадигма Linux - позволить нескольким простым инструментам взаимодействовать и решать ваши проблемы. Если нет дополнительных причин, по которым вам следует создавать демона (помимо периодического запуска), выберите другой подход.

Кроме того, если вы используете daemonize с циклом и происходит сбой, никто не проверит почту после этого (как указал Иван Невоструев в комментариях к этому ответу). Хотя, если скрипт добавлен как задание cron, он просто сработает снова.

68 голосов
/ 16 июня 2011

Вот хороший класс, который взят из здесь :

#!/usr/bin/env python

import sys, os, time, atexit
from signal import SIGTERM

class Daemon:
        """
        A generic daemon class.

        Usage: subclass the Daemon class and override the run() method
        """
        def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
                self.stdin = stdin
                self.stdout = stdout
                self.stderr = stderr
                self.pidfile = pidfile

        def daemonize(self):
                """
                do the UNIX double-fork magic, see Stevens' "Advanced
                Programming in the UNIX Environment" for details (ISBN 0201563177)
                http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
                """
                try:
                        pid = os.fork()
                        if pid > 0:
                                # exit first parent
                                sys.exit(0)
                except OSError, e:
                        sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
                        sys.exit(1)

                # decouple from parent environment
                os.chdir("/")
                os.setsid()
                os.umask(0)

                # do second fork
                try:
                        pid = os.fork()
                        if pid > 0:
                                # exit from second parent
                                sys.exit(0)
                except OSError, e:
                        sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
                        sys.exit(1)

                # redirect standard file descriptors
                sys.stdout.flush()
                sys.stderr.flush()
                si = file(self.stdin, 'r')
                so = file(self.stdout, 'a+')
                se = file(self.stderr, 'a+', 0)
                os.dup2(si.fileno(), sys.stdin.fileno())
                os.dup2(so.fileno(), sys.stdout.fileno())
                os.dup2(se.fileno(), sys.stderr.fileno())

                # write pidfile
                atexit.register(self.delpid)
                pid = str(os.getpid())
                file(self.pidfile,'w+').write("%s\n" % pid)

        def delpid(self):
                os.remove(self.pidfile)

        def start(self):
                """
                Start the daemon
                """
                # Check for a pidfile to see if the daemon already runs
                try:
                        pf = file(self.pidfile,'r')
                        pid = int(pf.read().strip())
                        pf.close()
                except IOError:
                        pid = None

                if pid:
                        message = "pidfile %s already exist. Daemon already running?\n"
                        sys.stderr.write(message % self.pidfile)
                        sys.exit(1)

                # Start the daemon
                self.daemonize()
                self.run()

        def stop(self):
                """
                Stop the daemon
                """
                # Get the pid from the pidfile
                try:
                        pf = file(self.pidfile,'r')
                        pid = int(pf.read().strip())
                        pf.close()
                except IOError:
                        pid = None

                if not pid:
                        message = "pidfile %s does not exist. Daemon not running?\n"
                        sys.stderr.write(message % self.pidfile)
                        return # not an error in a restart

                # Try killing the daemon process       
                try:
                        while 1:
                                os.kill(pid, SIGTERM)
                                time.sleep(0.1)
                except OSError, err:
                        err = str(err)
                        if err.find("No such process") > 0:
                                if os.path.exists(self.pidfile):
                                        os.remove(self.pidfile)
                        else:
                                print str(err)
                                sys.exit(1)

        def restart(self):
                """
                Restart the daemon
                """
                self.stop()
                self.start()

        def run(self):
                """
                You should override this method when you subclass Daemon. It will be called after the process has been
                daemonized by start() or restart().
                """
50 голосов
/ 21 октября 2009

Вы должны использовать библиотеку python-daemon , она обо всем позаботится.

Из PyPI: Библиотека для реализации хорошо работающего процесса демона Unix.

37 голосов
/ 21 октября 2009

Вы можете использовать fork (), чтобы отсоединить ваш скрипт от tty и продолжить его выполнение, например:

import os, sys
fpid = os.fork()
if fpid!=0:
  # Running as daemon now. PID is fpid
  sys.exit(0)

Конечно, вам также нужно реализовать бесконечный цикл, как

while 1:
  do_your_check()
  sleep(5)

Надеюсь, вы начали.

13 голосов
/ 22 октября 2013

Вы также можете запустить скрипт python как сервис, используя скрипт оболочки. Сначала создайте сценарий оболочки для запуска сценария Python следующим образом (имя сценария произвольное имя)

#!/bin/sh
script='/home/.. full path to script'
/usr/bin/python $script &

теперь создайте файл в /etc/init.d/scriptname

#! /bin/sh

PATH=/bin:/usr/bin:/sbin:/usr/sbin
DAEMON=/home/.. path to shell script scriptname created to run python script
PIDFILE=/var/run/scriptname.pid

test -x $DAEMON || exit 0

. /lib/lsb/init-functions

case "$1" in
  start)
     log_daemon_msg "Starting feedparser"
     start_daemon -p $PIDFILE $DAEMON
     log_end_msg $?
   ;;
  stop)
     log_daemon_msg "Stopping feedparser"
     killproc -p $PIDFILE $DAEMON
     PID=`ps x |grep feed | head -1 | awk '{print $1}'`
     kill -9 $PID       
     log_end_msg $?
   ;;
  force-reload|restart)
     $0 stop
     $0 start
   ;;
  status)
     status_of_proc -p $PIDFILE $DAEMON atd && exit 0 || exit $?
   ;;
 *)
   echo "Usage: /etc/init.d/atd {start|stop|restart|force-reload|status}"
   exit 1
  ;;
esac

exit 0

Теперь вы можете запускать и останавливать ваш скрипт на python, используя команду /etc/init.d/scriptname start или stop.

11 голосов
/ 22 октября 2013

cron - отличный выбор для многих целей. Однако он не создает службу или демон, как вы запросили в OP. cron просто запускает задания периодически (то есть задание запускается и останавливается), и не чаще, чем раз в минуту. Есть проблемы с cron - например, если предыдущий экземпляр вашего скрипта все еще выполняется в следующий раз, когда расписание cron запускается и запускает новый экземпляр, это нормально? cron не обрабатывает зависимости; он просто пытается начать работу, когда в расписании говорится.

Если вы обнаружите ситуацию, когда вам действительно нужен демон (процесс, который никогда не останавливается), взгляните на supervisord. Он предоставляет простой способ обернуть обычный, недемонизированный скрипт или программу и заставить его работать как демон. Это гораздо лучший способ, чем создание нативного демона Python.

9 голосов
/ 03 апреля 2016

Простая и поддерживаемая версия - Deamonize Установите его из Python Package Index (PyPI):

$ pip install daemonize

, а затем используйте как:

...
import os, sys
from daemonize import Daemonize
...
def main()
      # your code here

if __name__ == '__main__':
        myname=os.path.basename(sys.argv[0])
        pidfile='/tmp/%s' % myname       # any name
        daemon = Daemonize(app=myname,pid=pidfile, action=main)
        daemon.start()
8 голосов
/ 22 января 2012

как насчет использования команды $nohup в linux?

Я использую его для запуска команд на моем сервере Bluehost.

Пожалуйста, совет, если я ошибаюсь.

3 голосов
/ 26 января 2016

Если вы используете терминал (ssh или что-то в этом роде) и хотите, чтобы долгосрочный скрипт работал после выхода из терминала, вы можете попробовать это:

screen

apt-get install screen

создать виртуальный терминал внутри (а именно abc): screen -dmS abc

теперь мы подключаемся к abc: screen -r abc

Итак, теперь мы можем запустить скрипт Python: python Keep_sending_mail.py

отныне вы можете напрямую закрыть свой терминал, однако скрипт python будет продолжать работать, а не выключаться

Поскольку PID этого Keep_sending_mail.py принадлежит виртуальному экрану, а не Терминал (SSH) * * 1 022

Если вы хотите вернуться и проверить состояние запущенного скрипта, вы можете снова использовать screen -r abc

3 голосов
/ 21 октября 2009

Сначала прочитайте псевдонимы почты. Почтовый псевдоним будет делать это внутри почтовой системы без необходимости дурачиться с демонами или службами или чем-то в этом роде.

Вы можете написать простой скрипт, который будет выполняться sendmail каждый раз, когда почтовое сообщение отправляется на определенный почтовый ящик.

См. http://www.feep.net/sendmail/tutorial/intro/aliases.html

Если вы действительно хотите написать излишне сложный сервер, вы можете сделать это.

nohup python myscript.py &

Это все, что нужно. Ваш сценарий просто зацикливается и спит.

import time
def do_the_work():
    # one round of polling -- checking email, whatever.
while True:
    time.sleep( 600 ) # 10 min.
    try:
        do_the_work()
    except:
        pass
...