209 votes

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 transmet les nouveaux e-mails à un programme externe. Comment puis-je faire en sorte que ce script s'exécute 24 heures sur 24 et 7 jours sur 7, par exemple en le transformant en démon ou en service sous Linux. Aurais-je également besoin d'une boucle qui ne se termine jamais dans le programme, ou cela peut-il être fait en faisant simplement ré-exécuter le code plusieurs fois ?

1 votes

Voir la question de l'OS : stackoverflow.com/questions/1423345/

3 votes

"vérifie une certaine adresse e-mail et transmet les nouveaux e-mails à un programme externe" N'est-ce pas ce que fait sendmail ? Vous pouvez définir des alias de courrier pour diriger une boîte aux lettres vers un script. Pourquoi n'utilisez-vous pas les alias de courrier pour faire cela ?

2 votes

Sur un linux moderne qui a systemd vous pouvez créer un service systemd dans daemon comme décrit aquí . Voir aussi : freedesktop.org/software/systemd/man/systemd.service.html

105voto

Pavel Shved Points 34706

Vous avez deux options ici.

  1. Faites un bon travail de cron qui appelle votre script. Cron est un nom commun pour un démon GNU/Linux qui lance périodiquement des scripts selon un calendrier que vous définissez. Vous ajoutez votre script dans une crontab ou placez un lien symbolique vers celui-ci dans un répertoire spécial et le démon se charge de le lancer en arrière-plan. Vous pouvez en savoir plus à Wikipedia. Il existe une variété de démons cron différents, mais votre système GNU/Linux devrait l'avoir déjà installé.

  2. Utilisez une sorte de approche python (une bibliothèque, par exemple) pour que votre script puisse se démonétiser. Oui, il faudra une simple boucle d'événements (où vos événements sont des déclenchements de timers, éventuellement, fournis par la fonction sleep).

Je ne vous recommanderais pas de choisir la deuxième option, car vous répéteriez en fait 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 des raisons supplémentaires pour lesquelles vous devriez faire un démon (en plus du déclenchement périodique), choisissez l'autre approche.

De plus, si vous utilisez daemonize avec une boucle et qu'un crash se produit, personne ne vérifiera le courrier après cela (comme l'a fait remarquer Ivan Nevostruev dans les commentaires de este réponse). Alors que si le script est ajouté comme tâche cron, il se déclenchera simplement à nouveau.

10 votes

+1 au cronjob. Je ne pense pas que la question précise qu'il s'agit de vérifier un compte de messagerie local, donc les filtres de messagerie ne s'appliquent pas.

0 votes

Que se passe-t-il si l'on utilise une boucle sans terminaison dans un programme Python et qu'on l'enregistre ensuite dans le fichier crontab sera la liste ? Si j'établis une telle .py pour l'heure, cela va-t-il créer de nombreux processus qui ne seront jamais terminés ? Si c'est le cas, je pense que cela ressemblerait à un démon.

0 votes

Je peux voir que cron est une solution évidente si vous vérifiez les emails une fois par minute (ce qui est la résolution temporelle la plus basse pour Cron). Mais qu'en est-il si je veux vérifier les courriels toutes les 10 secondes ? Devrais-je écrire le script Python pour exécuter la requête 60 fois, ce qui signifie qu'elle se termine après 50 secondes, puis laisser cron relancer le script 10 secondes plus tard ?

77voto

the_drow Points 6141

Voici une belle classe qui est tirée de aquí :

#!/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().
                """

2 votes

Est-ce que c'est redémarrer quand le système est redémarré ? parce que quand le système est redémarré, le processus est tué, n'est-ce pas ?

0 votes

Pouvez-vous me dire comment je peux utiliser ce code pour exécuter mon programme en tant que démon ? Je ne comprends pas comment cela va fonctionner.

62voto

Prody Points 2440

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

De PyPI : Bibliothèque permettant d'implémenter un processus démon Unix qui se comporte bien.

4 votes

Idem pour le commentaire de Jorge Vargas. Après avoir examiné le code, il semble que ce soit un bon code, mais l'absence totale de documentation et d'exemples le rend très difficile à utiliser, ce qui signifie que la plupart des développeurs l'ignoreront à juste titre au profit d'alternatives mieux documentées.

1 votes

Il semble que cela ne fonctionne pas correctement dans Python 3.5 : gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2

0 votes

Ne fonctionne pas comme prévu. Ce serait bien s'il fonctionnait.

41voto

jhwist Points 5270

Vous pouvez utiliser fork() pour détacher votre script du tty et le faire continuer à s'exécuter, 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 aussi implémenter une boucle sans fin, comme par exemple

while 1:
  do_your_check()
  sleep(5)

J'espère que cela vous aidera à démarrer.

0 votes

Bonjour, j'ai essayé cette méthode et elle fonctionne pour moi. Mais lorsque je ferme le terminal ou que je sors de la session ssh, le script cesse également de fonctionner ! !!

0 votes

@DavidOkwii nohup / disown les commandes détacheraient le processus de la console et il ne mourrait pas. Ou vous pouvez le démarrer avec init.d

16voto

kishorekdty Points 61

Vous pouvez également faire en sorte que le script python s'exécute en tant que service en utilisant un script shell. Créez d'abord un shell script pour exécuter le python script comme ceci (nom du script nom arbitraire)

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

maintenant créez un fichier dans /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

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

0 votes

Je viens d'essayer ceci, et il s'avère que cela va démarrer le processus, mais il ne sera pas démonisé (c'est-à-dire qu'il sera toujours attaché au terminal). Cela fonctionnerait probablement bien si vous exécutez update-rc.d et que vous le faites tourner au démarrage (je suppose qu'il n'y a pas de terminal attaché lorsque ces scripts sont exécutés), mais cela ne fonctionne pas si vous l'invoquez manuellement. Il semble que supervisord soit une meilleure solution.

0 votes

On pourrait probablement utiliser disown en quelque sorte ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X