web-dev-qa-db-fra.com

Comment faire en sorte qu'un script Python s'exécute comme un service ou un démon sous Linux

J'ai écrit un script Python qui vérifie une adresse électronique donnée et transmet les nouveaux courriers électroniques à un programme externe. Comment puis-je exécuter ce script 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 finisse jamais dans le programme, ou cela peut-il être fait simplement en ré-exécutant le code plusieurs fois?

159
adhanlon

Vous avez deux options ici.

  1. Créez un travail cron approprié qui appelle votre script. Cron est un nom commun pour un démon GNU/Linux qui lance périodiquement des scripts selon une planification que vous avez définie. Vous ajoutez votre script dans une crontab ou placez un lien symbolique vers celle-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 sur Wikipedia. Il existe une variété de démons cron différents, mais votre système GNU/Linux devrait déjà l'avoir déjà installé.

  2. Utilisez une sorte d’approche python (une bibliothèque, par exemple) pour que votre script puisse se démoniser. Oui, il faudra une simple boucle d’événement (où vos événements déclenchent le minuteur, éventuellement, fournis par la fonction de veille).

Je ne vous recommanderais pas de choisir 2., car vous répéteriez en fait la fonctionnalité cron. Le paradigme du système Linux consiste à laisser plusieurs outils simples interagir et résoudre vos problèmes. À moins qu'il y ait des raisons supplémentaires pour lesquelles vous devriez créer un démon (en plus du déclenchement périodique), choisissez une 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 indiqué par Ivan Nevostruev dans les commentaires de this answer). Tandis que si le script est ajouté en tant que tâche cron, il se déclenchera à nouveau.

92
P Shved

Voici une classe de Nice 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().
                """
70
the_drow

Vous devriez utiliser la bibliothèque python-daemon , elle s’occupe de tout.

De PyPI: Bibliothèque pour implémenter un processus démon Unix bien conçu.

54
Prody

Vous pouvez utiliser fork () pour détacher votre script du tty et le laisser continuer à s'exécuter, comme suit:

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 implémenter une boucle sans fin, comme

while 1:
  do_your_check()
  sleep(5)

J'espère que ça commence.

38
jhwist

Vous pouvez également exécuter le script python en tant que service à l'aide d'un script Shell. Commencez par créer un script Shell pour exécuter le script python comme ceci (nom arbitraire du nom du script).

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

Vous pouvez maintenant démarrer et arrêter votre script python à l'aide de la commande /etc/init.d/scriptname start or stop.

15
Kishore K

cron est clairement un excellent choix pour de nombreuses raisons. Cependant, il ne crée pas de service ou de démon comme vous l'avez demandé dans l'OP. cron exécute uniquement les travaux périodiquement (c'est-à-dire 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 précédente de votre script est toujours en cours d'exécution la prochaine fois que la planification cron entre et lance une nouvelle instance, est-ce correct? cron ne gère pas les dépendances; il essaie juste de commencer un travail quand l'horaire le dit.

Si vous trouvez une situation dans laquelle vous avez vraiment besoin d'un démon (un processus qui ne s'arrête jamais de s'exécuter), consultez supervisord. Il fournit un moyen simple d'encapsuler un script ou un programme normal non démonisé et de le faire fonctionner comme un démon. C'est un bien meilleur moyen que de créer un démon natif Python.

12
Chris Johnson

Un simple et supporté version est Daemonize.

Installez-le à partir de Python Index de paquet (PyPI):

$ pip install daemonize

et ensuite utiliser comme:

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

if __== '__main__':
        myname=os.path.basename(sys.argv[0])
        pidfile='/tmp/%s' % myname       # any name
        daemon = Daemonize(app=myname,pid=pidfile, action=main)
        daemon.start()
10
fcm

comment utiliser $Nohup commande sur linux?

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

S'il vous plaît des conseils si je me trompe.

8
faisal00813

Si vous utilisez un terminal (ssh ou autre chose) et que vous voulez conserver un script de longue durée 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 on se connecte à abc: screen -r abc

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

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

Puisque ce PID de Keep_sending_mail.py appartient à l’écran virtuel et non au terminal (ssh)

Si vous souhaitez revenir en arrière pour vérifier l'état d'exécution du script, vous pouvez utiliser à nouveau screen -r abc.

4
Microos

Commencez par lire les alias de messagerie. Un alias de messagerie le fera dans le système de messagerie sans que vous ayez à vous occuper de démons, de services ou quoi que ce soit du 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 serveur inutilement complexe, vous pouvez le faire.

Nohup python myscript.py &

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

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
S.Lott

Utilisez le gestionnaire de services proposé par votre système - par exemple sous Ubuntu, utilisez version récente. Ceci gérera tous les détails pour vous, tels que démarrer au démarrage, redémarrer en cas de crash, etc.

1
Richard

Je recommanderais 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
Fomalhaut

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 framework Cement : Le travail sur les cadres de ciment est un travail de cadre CLI sur lequel vous pouvez déployer votre application.

interface de 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()

Votre classe App.py:

 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 être exécutée sur un fil pour être démon.

Pour exécuter l'application, faites ceci en ligne de commande

python interface.py --help

0
Manouchehr Rasouli