Comment faire fonctionner un script Python comme un service ou un démon sous Linux

j'ai écrit un script Python qui vérifie une certaine adresse e-mail et passe de nouveaux e-mails à un programme externe. Comment puis-je faire exécuter ce script 24h / 24, 7j / 7, par exemple en le transformant en démon ou en service sous Linux? Est-ce que j'aurais aussi besoin d'une boucle qui ne se termine jamais dans le programme, ou est-ce que cela peut être fait en faisant simplement exécuter le code plusieurs fois?

144
demandé sur Martin Thoma 2009-10-21 23:36:34

15 réponses

vous avez deux options ici.

  1. Faire un bon cron qui appelle votre script. Cron est un nom commun pour un démon GNU/Linux qui lance périodiquement des scripts selon un horaire que vous avez défini. Vous ajoutez votre script dans une crontab ou placez un lien symbolique vers lui dans un répertoire spécial et le démon gère la tâche de le lancer en arrière-plan. Vous pouvez lire la suite sur wikipedia. Y est une variété de démons cron différents, mais votre système GNU / Linux devrait déjà l'avoir installé.

  2. utilisez une sorte de approche python (une bibliothèque, par exemple) pour que votre script puisse se démoniser lui-même. Oui, il vous faudra une boucle d'événements simple (où vos événements sont déclenchés par un minuteur, éventuellement fourni par la fonction de sommeil).

Je ne vous recommande pas de choisir 2. parce que vous répétez la fonctionnalité cron. Le paradigme du système Linux est de laisser plusieurs outils simples interagir et résoudre vos problèmes. À moins qu'il n'y ait d'autres raisons pour lesquelles vous devriez faire un démon (en plus de déclencher périodiquement), choisissez l'autre approche.

aussi, si vous utilisez daemonize avec une boucle et un accident se produit, personne ne vérifiera le courrier après que (comme souligné par Ivan Nevostruev dans les commentaires à cette réponse ). Alors que si le script est ajouté comme une tâche cron, il se déclenchera à nouveau.

85
répondu P Shved 2017-05-23 12:02:56

Voici une classe agréable qui est tirée de ici :

#!/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().
                """
61
répondu the_drow 2016-04-19 05:00:32

vous devez utiliser la bibliothèque python-daemon , elle s'occupe de tout.

De PyPI: Bibliothèque à mettre en œuvre une sage Unix processus de démon.

50
répondu Prody 2016-08-30 15:53:47

vous pouvez utiliser fork() pour détacher votre script du tty et le faire continuer à fonctionner, comme ceci:

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

bien sûr, vous devez également mettre en œuvre une boucle sans fin, comme

while 1:
  do_your_check()
  sleep(5)

J'espère que vous avez commencé.

37
répondu jhwist 2009-10-21 19:45:12

vous pouvez également faire fonctionner le script python comme un service en utilisant un script shell. Créez d'abord un script shell pour exécuter le script python comme ceci (nom de script arbitary)

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

crée maintenant un fichier dans /etc/init.d/nomduscript

#! /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 "" 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 }'`
     kill -9 $PID       
     log_end_msg $?
   ;;
  force-reload|restart)
     "151910920" stop
     "151910920" 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

Maintenant vous pouvez démarrer et arrêter votre script python en utilisant la commande /etc/init.d/nomduscript démarrer ou arrêter.

12
répondu Kishore K 2013-10-22 09:56:08

Que diriez-vous d'utiliser la commande $nohup sur linux?

Je l'utilise pour exécuter mes commandes sur mon serveur Bluehost.

s'il vous plaît, si je me trompe.

7
répondu faisal00813 2012-01-21 21:07:50

une version simple et pris en charge est Désamonisé Installez-le à partir de L'Index des paquets Python (PyPI):

$ pip install daemonize

et ensuite utiliser comme:

...
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()
7
répondu fcm 2017-05-04 17:30:01

cron est clairement un grand choix pour de nombreuses raisons. Cependant, il ne crée pas un service ou un démon comme vous l'avez demandé dans L'OP. cron exécute juste des travaux périodiquement (ce qui signifie que le travail commence et s'arrête), et pas plus d'une fois / minute. Il y a des problèmes avec cron -- par exemple, si une instance antérieure de votre script est toujours en cours d'exécution la prochaine fois que le programme cron arrive et lance une nouvelle instance, est-ce correct? cron ne gère pas dépendances; il essaie juste de commencer un travail lorsque le calendrier dit.

si vous trouvez une situation où vous avez vraiment besoin d'un démon (un processus qui ne cesse jamais de fonctionner), jetez un oeil à supervisord . Il fournit un moyen simple pour envelopper un script ou un programme normal, non-daemonisé et le faire fonctionner comme un daemon. C'est une bien meilleure façon que de créer un natif Python démon.

6
répondu Chris Johnson 2013-10-22 10:36:53

d'abord, lisez sur les alias de courrier. Un alias mail le fera à l'intérieur du système de messagerie sans que vous ayez à jouer avec des démons ou des services ou quoi que ce soit de ce genre.

vous pouvez écrire un script simple qui sera exécuté par sendmail chaque fois qu'un message est envoyé à une boîte aux lettres spécifique.

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

Si vous voulez vraiment écrire un inutilement complexe serveur, vous pouvez le faire.

nohup python myscript.py &

C'est tout ce qu'il faut. Votre script boucle et dort.

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
3
répondu S.Lott 2009-10-21 20:11:41

si vous utilisez terminal(SSH ou quelque chose du genre) et que vous voulez garder un script de longue durée qui fonctionne après que vous vous êtes déconnecté du terminal, vous pouvez essayer ceci:

screen

apt-get install screen

créer un terminal virtuel à l'intérieur( à savoir abc): screen -dmS abc

maintenant nous nous connectons à abc: screen -r abc

donc, maintenant nous pouvons exécuter le script python: python Keep_sending_mail.py

à partir de Maintenant, vous pouvez fermer directement votre terminal, cependant, le script python continuera d'exécuter plutôt que d'être arrêté

étant donné que ce PID Keep_sending_mail.py appartient à l'écran virtuel plutôt qu'au terminal (ssh)

si vous voulez retourner vérifier l'état d'exécution de votre script, vous pouvez utiliser screen -r abc à nouveau

2
répondu Microos 2016-01-26 06:59:40

utilisez n'importe quel gestionnaire de service que votre système offre - par exemple sous Ubuntu, utilisez upstart . Cela gérera tous les détails pour vous tels que démarrer au démarrage, Redémarrer au crash, etc.

1
répondu Richard 2014-01-03 16:36:35

je recommande cette solution. Vous devez hériter et remplacer la méthode run .

import sys
import os
from signal import SIGTERM
from abc import ABCMeta, abstractmethod



class Daemon(object):
    __metaclass__ = ABCMeta


    def __init__(self, pidfile):
        self._pidfile = pidfile


    @abstractmethod
    def run(self):
        pass


    def _daemonize(self):
        # decouple threads
        pid = os.fork()

        # stop first thread
        if pid > 0:
            sys.exit(0)

        # write pid into a pidfile
        with open(self._pidfile, 'w') as f:
            print >> f, os.getpid()


    def start(self):
        # if daemon is started throw an error
        if os.path.exists(self._pidfile):
            raise Exception("Daemon is already started")

        # create and switch to daemon thread
        self._daemonize()

        # run the body of the daemon
        self.run()


    def stop(self):
        # check the pidfile existing
        if os.path.exists(self._pidfile):
            # read pid from the file
            with open(self._pidfile, 'r') as f:
                pid = int(f.read().strip())

            # remove the pidfile
            os.remove(self._pidfile)

            # kill daemon
            os.kill(pid, SIGTERM)

        else:
            raise Exception("Daemon is not started")


    def restart(self):
        self.stop()
        self.start()
1
répondu Fomalhaut 2015-05-09 21:31:33

avait un problème similaire, le lien ci-dessous a bien fonctionné pour moi: http://werxltd.com/wp/2012/01/05/simple-init-d-script-template/#footnote_0_1077

il n'utilise rien de spécifique à un distributeur, si chkconfig utilisé, peut être lancé au démarrage du système.

0
répondu MasterAler 2014-12-31 05:36:22

pour créer quelque chose qui fonctionne comme un service, vous pouvez utiliser cette chose :

la première chose que vous devez faire est d'installer le ciment cadre: Le travail de cadre de ciment est un travail de cadre de CLI que vous pouvez déployer votre application sur elle.

interface en ligne de commande de l'application:

interface.py

 from cement.core.foundation import CementApp
 from cement.core.controller import CementBaseController, expose
 from YourApp import yourApp

 class Meta:
    label = 'base'
    description = "your application description"
    arguments = [
        (['-r' , '--run'],
          dict(action='store_true', help='Run your application')),
        (['-v', '--version'],
          dict(action='version', version="Your app version")),
        ]
        (['-s', '--stop'],
          dict(action='store_true', help="Stop your application")),
        ]

    @expose(hide=True)
    def default(self):
        if self.app.pargs.run:
            #Start to running the your app from there !
            YourApp.yourApp()
        if self.app.pargs.stop:
            #Stop your application
            YourApp.yourApp.stop()

 class App(CementApp):
       class Meta:
       label = 'Uptime'
       base_controller = 'base'
       handlers = [MyBaseController]

 with App() as app:
       app.run()

YourApp.py classe:

 import threading

 class yourApp:
     def __init__:
        self.loger = log_exception.exception_loger()
        thread = threading.Thread(target=self.start, args=())
        thread.daemon = True
        thread.start()

     def start(self):
        #Do every thing you want
        pass
     def stop(self):
        #Do some things to stop your application

gardez à l'esprit que votre application doit fonctionner sur un fil pour être démon

pour lancer l'application il suffit de faire ceci en ligne de commande

python interface.py -- aide

0
répondu Manouchehr Rasouli 2017-08-07 10:00:09