So lassen Sie ein Python-Skript unter Linux wie einen Dienst oder Daemon laufen

175

Ich habe ein Python-Skript geschrieben, das eine bestimmte E-Mail-Adresse überprüft und neue E-Mails an ein externes Programm weiterleitet. Wie kann ich dieses Skript rund um die Uhr ausführen lassen, z. B. um es in einen Daemon oder einen Dienst unter Linux umzuwandeln? Würde ich auch eine Schleife benötigen, die niemals im Programm endet, oder kann dies durch mehrmaliges Ausführen des Codes erfolgen?

Adhanlon
quelle
3
"Überprüft eine bestimmte E-Mail-Adresse und leitet neue E-Mails an ein externes Programm weiter." Ist es nicht das, was sendmail tut? Sie können einen E-Mail-Alias ​​definieren, um ein Postfach an ein Skript weiterzuleiten. Warum verwenden Sie dazu keine Mail-Aliase?
S.Lott
2
Unter einem modernen Linux systemdkönnen Sie einen systemd-Dienst im hierdaemon beschriebenen Modus erstellen . Siehe auch: freedesktop.org/software/systemd/man/systemd.service.html
ccpizza
Wenn das Linux - System unterstützt systemd, verwenden Sie den Ansatz hier skizziert .
Gerardw

Antworten:

96

Sie haben hier zwei Möglichkeiten.

  1. Machen Sie einen richtigen Cron-Job , der Ihr Skript aufruft. Cron ist ein gebräuchlicher Name für einen GNU / Linux-Daemon, der regelmäßig Skripte nach einem von Ihnen festgelegten Zeitplan startet. Sie fügen Ihr Skript einer Crontab hinzu oder platzieren einen Symlink dazu in einem speziellen Verzeichnis, und der Dämon übernimmt die Aufgabe, es im Hintergrund zu starten. Sie können mehr bei Wikipedia lesen . Es gibt verschiedene Cron-Daemons, aber auf Ihrem GNU / Linux-System sollte es bereits installiert sein.

  2. Verwenden Sie einen Python-Ansatz (z. B. eine Bibliothek), damit Ihr Skript sich selbst dämonisieren kann. Ja, es ist eine einfache Ereignisschleife erforderlich (bei der Ihre Ereignisse einen Timer auslösen, möglicherweise bereitgestellt durch die Sleep-Funktion).

Ich würde Ihnen nicht empfehlen, 2. zu wählen, da Sie tatsächlich die Cron-Funktionalität wiederholen würden. Das Linux-Systemparadigma besteht darin, mehrere einfache Tools interagieren zu lassen und Ihre Probleme zu lösen. Wählen Sie den anderen Ansatz, es sei denn, es gibt zusätzliche Gründe, warum Sie einen Dämon erstellen sollten (zusätzlich zum regelmäßigen Auslösen).

Wenn Sie Daemonize mit einer Schleife verwenden und ein Absturz auftritt, überprüft danach niemand mehr die E-Mails (wie von Ivan Nevostruev in den Kommentaren zu dieser Antwort hervorgehoben). Wenn das Skript als Cron-Job hinzugefügt wird, wird es nur erneut ausgelöst.

P Shved
quelle
7
+1 zum Cronjob. Ich glaube nicht, dass die Frage angibt, dass ein lokales E-Mail-Konto überprüft wird, daher gelten keine E-Mail-Filter
John La Rooy,
Was passiert, crontabwenn eine Schleife ohne Beendigung in einem Python-Programm verwendet und dann in der Liste registriert wird? Wenn ich solche .pyfür stündlich einrichte , werden dadurch viele Prozesse erstellt, die niemals beendet werden? Wenn ja, denke ich, würde dies sehr gerne Daemon.
Veck Hsiao
Ich kann sehen, dass cron eine offensichtliche Lösung ist, wenn Sie einmal pro Minute nach E-Mails suchen (was die niedrigste Zeitauflösung für Cron ist). Aber was ist, wenn ich alle 10 Sekunden nach E-Mails suchen möchte? Sollte ich das Python-Skript schreiben, um die Abfrage 60 Mal auszuführen, was bedeutet, dass es nach 50 Sekunden endet, und dann das Skript 10 Sekunden später erneut starten lassen?
Mads Skjern
Ich habe nicht mit Daemons / Diensten gearbeitet, aber ich hatte den Eindruck, dass es (OS / init / init.d / upstart oder wie es heißt) dafür sorgt, dass ein Daemon neu gestartet wird, wenn / wenn er endet / abstürzt.
Mads Skjern
@VeckHsiao ja, crontab ruft ein Skript auf, so dass viele Instanzen Ihres Python-Skripts mit jeder seiner Schleife aufgerufen werden ....
Pipo
71

Hier ist eine schöne Klasse, die von hier genommen wird :

#!/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().
                """
the_drow
quelle
1
Wird es neu gestartet, wenn das System neu gestartet wird? denn wenn das System neu gestartet wird, wird der Prozess abgebrochen, oder?
ShivaPrasad
58

Sie sollten die Python-Daemon- Bibliothek verwenden, sie kümmert sich um alles.

Von PyPI: Library zur Implementierung eines gut verhaltenen Unix-Daemon-Prozesses.

Prody
quelle
3
Ditto Jorge Vargas Kommentar. Nach dem Betrachten des Codes sieht es tatsächlich wie ein ziemlich schönes Stück Code aus, aber das völlige Fehlen von Dokumenten und Beispielen macht es sehr schwierig, es zu verwenden, was bedeutet, dass die meisten Entwickler es zu Recht ignorieren, um besser dokumentierte Alternativen zu finden.
Cerin
1
Scheint in Python 3.5 nicht richtig zu funktionieren: gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2
Martin Thoma
Funktioniert nicht wie erwartet. Wäre es schön, wenn es so wäre?
Harlin
Unix! = Linux - könnte das das Problem sein?
Dana
39

Sie können fork () verwenden, um Ihr Skript vom tty zu trennen und es weiterhin ausführen zu lassen, wie folgt:

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

Natürlich müssen Sie auch eine Endlosschleife implementieren, wie z

while 1:
  do_your_check()
  sleep(5)

Ich hoffe, Sie haben damit begonnen.

jhwist
quelle
Hallo, ich habe es versucht und es funktioniert für mich. Aber wenn ich das Terminal schließe oder die SSH-Sitzung verlasse, funktioniert das Skript auch nicht mehr !!
David Okwii
@DavidOkwii nohup/ disownBefehle würden den Prozess von der Konsole trennen und er würde nicht sterben. Oder Sie könnten es mit init.d
pholat
14

Sie können das Python-Skript auch mithilfe eines Shell-Skripts als Dienst ausführen lassen. Erstellen Sie zuerst ein Shell-Skript, um das Python-Skript wie folgt auszuführen (Skriptname Arbitary Name)

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

Erstellen Sie jetzt eine Datei in /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

Jetzt können Sie Ihr Python-Skript mit dem Befehl /etc/init.d/scriptname starten oder stoppen.

Kishore K.
quelle
Ich habe es gerade versucht, und es stellt sich heraus, dass dies den Prozess startet, aber nicht dämonisiert wird (dh es ist immer noch an das Terminal angeschlossen). Es würde wahrscheinlich gut funktionieren, wenn Sie update-rc.d ausführen und es beim Booten ausführen lassen würden (ich nehme an, dass beim Ausführen dieser Skripte kein Terminal angeschlossen ist), aber es funktioniert nicht, wenn Sie es manuell aufrufen. Supervisord scheint eine bessere Lösung zu sein.
Ryuusenshi
13

Eine einfache und unterstützte Version ist Daemonize.

Installieren Sie es vom Python Package Index (PyPI):

$ pip install daemonize

und dann benutze wie:

...
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()
fcm
quelle
1
Wird es neu gestartet, wenn das System neu gestartet wird? denn wenn das System neu gestartet wird, wird der Prozess abgebrochen, oder?
ShivaPrasad
@ ShivaPrasad Hast du die Antwort darauf gefunden?
1UC1F3R616
Neustart nach einem Systemneustart ist keine Dämonenfunktion. Verwenden Sie cron, systemctl, Start-Hooks oder andere Tools, um Ihre App beim Start auszuführen.
fcm
1
@Kush Ja, ich wollte nach dem Neustart des Systems neu starten oder ähnliche Befehle verwenden. Ich habe systemd-Funktionen verwendet. Wenn Sie versuchen möchten, überprüfen Sie diesen Zugriff. Redhat.com/documentation/en-us/red_hat_enterprise_linux/…
ShivaPrasad
@ShivaPrasad Danke bro
1UC1F3R616
12

cronist eindeutig eine gute Wahl für viele Zwecke. Es wird jedoch kein Dienst oder Dämon erstellt, wie Sie es im OP angefordert haben. cronführt Jobs nur regelmäßig aus (dh der Job startet und stoppt) und nicht öfter als einmal / Minute. Es gibt Probleme mit cron- Wenn beispielsweise eine vorherige Instanz Ihres Skripts beim nächsten Auftreten des cronZeitplans und beim Starten einer neuen Instanz noch ausgeführt wird, ist das in Ordnung? cronbehandelt keine Abhängigkeiten; Es wird nur versucht, einen Job zu starten, wenn der Zeitplan dies vorschreibt.

Wenn Sie eine Situation finden, in der Sie wirklich einen Daemon benötigen (ein Prozess, der nie aufhört zu laufen), schauen Sie sich das an supervisord. Es bietet eine einfache Möglichkeit, ein normales, nicht daemonisiertes Skript oder Programm zu verpacken und es wie einen Daemon arbeiten zu lassen. Dies ist ein viel besserer Weg als das Erstellen eines nativen Python-Daemons.

Chris Johnson
quelle
9

Wie wäre es mit $nohupBefehl unter Linux?

Ich verwende es zum Ausführen meiner Befehle auf meinem Bluehost-Server.

Bitte beraten Sie, wenn ich falsch liege.

faisal00813
quelle
Ich benutze das auch, funktioniert wie ein Zauber. "Bitte raten Sie, wenn ich falsch liege."
Alexandre Mazel
5

Wenn Sie ein Terminal (ssh oder etwas anderes) verwenden und ein Langzeitskript nach dem Abmelden vom Terminal weiterhin verwenden möchten, können Sie Folgendes versuchen:

screen

apt-get install screen

Erstellen Sie ein virtuelles Terminal im Inneren (nämlich abc): screen -dmS abc

jetzt verbinden wir uns mit abc: screen -r abc

Jetzt können wir das Python-Skript ausführen: python keep_sending_mails.py

Von nun an können Sie Ihr Terminal direkt schließen. Das Python-Skript wird jedoch weiterhin ausgeführt und nicht heruntergefahren

Da dies keep_sending_mails.pydie PID ist, handelt es sich eher um einen untergeordneten Prozess des virtuellen Bildschirms als um das Terminal (ssh).

Wenn Sie zurückgehen möchten, überprüfen Sie den Ausführungsstatus Ihres Skripts. Sie können ihn screen -r abcerneut verwenden

Microos
quelle
2
Während dies funktioniert, ist es sehr schnell und schmutzig und sollte in der Produktion vermieden werden
pcnate
3

Informieren Sie sich zunächst über Mail-Aliase. Ein Mail-Alias ​​erledigt dies innerhalb des Mail-Systems, ohne dass Sie mit Dämonen oder Diensten oder Ähnlichem herumspielen müssen.

Sie können ein einfaches Skript schreiben, das von sendmail jedes Mal ausgeführt wird, wenn eine E-Mail-Nachricht an ein bestimmtes Postfach gesendet wird.

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

Wenn Sie wirklich einen unnötig komplexen Server schreiben möchten, können Sie dies tun.

nohup python myscript.py &

Das ist alles was es braucht. Ihr Skript wird einfach wiederholt und schläft.

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
S.Lott
quelle
6
Das Problem hier ist, dass do_the_work()das Skript abstürzen kann und niemand es erneut ausführen kann
Ivan Nevostruev
Wenn die Funktion do_the_work () abstürzt, wird sie nach 10 Minuten erneut aufgerufen, da nur der eine Funktionsaufruf einen Fehler auslöst. Aber anstatt die Schleife zum Absturz tryzu bringen, schlägt nur das Teil fehl und das except:Teil wird stattdessen aufgerufen (in diesem Fall nichts), aber die Schleife wird fortgesetzt und versucht weiterhin, die Funktion aufzurufen.
Sarbot
2

Ich würde diese Lösung empfehlen. Sie müssen die Methode erben und überschreiben 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()
Fomalhaut
quelle
2

Um etwas zu erstellen, das wie ein Dienst ausgeführt wird, können Sie Folgendes verwenden:

Das erste, was Sie tun müssen, ist die Installation des Cement- Frameworks: Cement-Frame-Arbeit ist eine CLI-Frame-Arbeit, auf der Sie Ihre Anwendung bereitstellen können.

Befehlszeilenschnittstelle der App:

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-Klasse:

 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

Beachten Sie, dass Ihre App auf einem Thread ausgeführt werden muss, um ein Daemon zu sein

Um die App auszuführen, tun Sie dies einfach in der Befehlszeile

python interface.py --help

Manouchehr Rasouli
quelle
2

Angenommen, Sie möchten wirklich, dass Ihre Schleife rund um die Uhr als Hintergrunddienst ausgeführt wird

Für eine Lösung, bei der Sie Ihrem Code keine Bibliotheken hinzufügen müssen, können Sie einfach eine Dienstvorlage erstellen, da Sie Linux verwenden:

Geben Sie hier die Bildbeschreibung ein

Legen Sie diese Datei (normalerweise /etc/systemd/system/) in Ihrem Daemon-Dienstordner ab und installieren Sie sie mit den folgenden systemctl-Befehlen (für die wahrscheinlich sudo-Berechtigungen erforderlich sind):

systemctl enable <service file name without extension>

systemctl daemon-reload

systemctl start <service file name without extension>

Sie können dann mit dem folgenden Befehl überprüfen, ob Ihr Dienst ausgeführt wird:

systemctl | grep running
Heitor Castro
quelle
1

Verwenden Sie den von Ihrem System angebotenen Service Manager - verwenden Sie beispielsweise unter Ubuntu upstart . Dadurch werden alle Details für Sie behandelt, z. B. Start beim Booten, Neustart beim Absturz usw.

Richard
quelle