如何使Python脚本像Linux中的服务或守护程序一样运行


175

我已经编写了一个Python脚本,该脚本检查特定的电子邮件地址并将新的电子邮件传递给外部程序。如何获得此脚本以执行24/7,例如在Linux中将其转换为守护程序或服务。我是否还需要一个永无休止的循环,还是只需要多次重新执行代码就可以完成?



3
“检查某个电子邮件地址并将新的电子邮件传递给外部程序”这不是sendmail做什么的吗?您可以定义邮件别名以将邮箱路由到脚本。您为什么不使用邮件别名来执行此操作?
S.Lott

2
在具有此功能的现代linux上,systemd您可以daemon此处所述在模式下创建systemd服务。另请参见:freedesktop.org/software/systemd/man/systemd.service.html
ccpizza

如果linux系统支持systemd,请使用此处概述的方法。
gerardw '18

Answers:


96

您在这里有两个选择。

  1. 进行适当的cron作业来调用您的脚本。Cron是GNU / Linux守护程序的通用名称,该守护程序会根据您设置的时间表定期启动脚本。您将脚本添加到crontab中,或将其符号链接放置到特殊目录中,守护程序将在后台启动该脚本。您可以在Wikipedia上阅读更多内容。有各种不同的cron守护程序,但是您的GNU / Linux系统应该已经安装了它。

  2. 对您的脚本使用某种python方法(例如,一个库)可以使其自身守护进程。是的,这将需要一个简单的事件循环(您的事件可能是计时器触发的,可能由睡眠功能提供)。

我不建议您选择2.,因为实际上您将重复cron功能。Linux系统范例是让多个简单的工具交互并解决您的问题。除非有其他原因(除了定期触发)之外,您还应创建守护程序,否则请选择其他方法。

另外,如果将daemonize与循环一起使用,并且发生崩溃,此后没有人会检查邮件(如Ivan Nevostruev对此答案的评论中指出的)。如果将脚本添加为cron作业,它将再次触发。


7
为cronjob +1。我认为问题不在于它正在检查本地邮件帐户,因此邮件过滤器不适用
John La Rooy

在不终止的情况下在Python程序中使用循环然后将其注册到crontab列表中会发生什么情况?如果我.py每小时设置一次,是否会创建许多永远不会终止的进程?如果是这样,我认为这很像守护程序。
萧敬腾

如果您每分钟检查一次电子邮件检查(这是Cron的最低时间分辨率),我可以看到cron是一个明显的解决方案。但是,如果我想每10秒检查一次电子邮件怎么办?我是否应该编写Python脚本来运行60次查询,这意味着它在50秒后结束,然后让cron在10秒后再次启动该脚本?
Mads Skjern

我没有使用过守护程序/服务,但是给我的印象是,它(OS / init / init.d / upstart或它的名字)负责在守护程序结束/崩溃时重新启动它。
Mads Skjern

@VeckHsiao是的,crontab调用了一个脚本,因此每个人的循环都将调用您的python脚本的许多实例....
Pipo

71

这是一个不错的课程,它是从这里获取的

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

1
重新启动系统后会重新启动吗?因为当系统重新启动时,该过程将被杀死,对吗?
ShivaPrasad

58

您应该使用python-daemon库,它可以处理所有事情。

来自PyPI:库,用于实现行为良好的Unix守护进程。


3
同上豪尔赫·巴尔加斯的评论。在看完代码之后,它实际上看起来像是一段不错的代码,但是完全缺少文档和示例,这使得它很难使用,这意味着大多数开发人员理应忽略它,以获取更好的文档化替代方案。
塞林2012年

1
似乎无法在Python 3.5中正常工作:gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2
Martin Thoma

无法正常工作。如果这样做会很好吗。
哈林

Unix!= Linux-麻烦吗?
达娜(Dana)

39

您可以使用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)

希望这可以帮助您开始。


您好,我已经尝试过了,并且对我有用。但是,当我关闭终端或退出ssh会话时,脚本也将停止工作!
大卫·奥克威

@DavidOkwii nohup/ disown命令将使进程与控制台分离,并且不会死。或者,您可以使用init.d来启动它
pholat'9

14

您还可以使用Shell脚本使python脚本作为服务运行。首先创建一个shell脚本来像这样运行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

现在,您可以使用命令/etc/init.d/scriptname start或stop启动和停止python脚本。


我只是尝试了一下,事实证明这将启动该过程,但不会被守护(即,它仍附加在终端上)。如果您运行update-rc.d并使其在启动时运行,则可能会正常工作(我假设运行这些脚本时没有连接终端),但如果手动调用它,则无法正常工作。似乎受监管者可能是更好的解决方案。
ryuusenshi 2014年

13

一个简单且受支持的版本Daemonize

从Python软件包索引(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()

1
重新启动系统后会重新启动吗?因为当系统重新启动时,该过程将被杀死,对吗?
ShivaPrasad

@ShivaPrasad你找到答案了吗?
1UC1F3R616

系统重启后重启不是恶魔功能。使用cron,systemctl,启动挂钩或其他工具在启动时运行您的应用。
fcm

1
@Kush是我想要的系统重启或使用像我用systemd功能的命令后重新启动,如果想尝试检查此access.redhat.com/documentation/en-us/red_hat_enterprise_linux/...
ShivaPrasad

@ShivaPrasad,谢谢兄弟
1UC1F3R616

12

cron显然,在许多方面都是不错的选择。但是,它不会按照您在OP中的请求创建服务或守护程序。 cron只是周期性地运行作业(意味着作业开始和停止),并且不超过一次/分钟。出现问题cron-例如,如果您的脚本的先前实例在下次cron计划表出现并启动新实例时仍在运行,可以吗? cron不处理依赖关系;时间表说的话,它只是试图开始工作。

如果发现确实需要守护程序的情况(一个永不停止运行的进程),请看一下supervisord。它提供了一种简单的方法来包装普通的,非守护进程的脚本或程序,并使其像守护进程一样运行。这比创建本地Python守护程序更好。


9

$nohup在Linux上使用命令怎么样?

我使用它在Bluehost服务器上运行命令。

如果我错了,请指教。


我也用它,就像一个魅力。“如果我错了,请提出建议。”
Alexandre Mazel

5

如果您正在使用终端(ssh或其他东西),并且想要从终端注销后保持长时间运行的脚本,则可以尝试以下操作:

screen

apt-get install screen

在内部创建一个虚拟终端(即abc): screen -dmS abc

现在我们连接到abc: screen -r abc

因此,现在我们可以运行python脚本了: python keep_sending_mails.py

从现在开始,您可以直接关闭终端,但是python脚本将继续运行而不是被关闭

由于此keep_sending_mails.pyPID是虚拟屏幕的子进程,而不是终端(ssh)

如果要返回以检查脚本的运行状态,可以screen -r abc再次使用


2
尽管这
可行

3

首先,阅读邮件别名。邮件别名将在邮件系统内执行此操作,而您无需四处寻找守护程序或服务或任何类似的内容。

您可以编写一个简单的脚本,该脚本将在每次将邮件发送到特定邮箱时由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

6
这里的问题是,这do_the_work()可能会使脚本崩溃并导致没人再次运行它
Ivan Nevostruev 09年

如果函数do_the_work()崩溃,则将在10分钟后再次调用它,因为只有一个函数调用会引发错误。但是,除了不会使循环崩溃之外try,该except:部分还可以发生故障,而该部分将被调用(在这种情况下,则为空),但是循环将继续并继续尝试调用该函数。
sarbot '18年

2

我会推荐这种解决方案。您需要继承和重写method 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()

2

创建一些像服务一样运行的东西,您可以使用以下东西:

您必须做的第一件事是安装Cement框架:Cement框架是一个CLI框架,您可以在其上部署应用程序。

应用程序的命令行界面:

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

 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

请记住,您的应用必须在线程上运行才能成为守护进程

要运行该应用程序,只需在命令行中执行此操作

python interface.py-帮助


1

使用系统提供的任何服务管理器-例如在Ubuntu下使用upstart。这将为您处理所有详细信息,例如启动时启动,崩溃时重启等。


0

假设您真的希望循环将24/7作为后台服务运行

对于不涉及使用库注入代码的解决方案,您可以简单地创建一个服务模板,因为您使用的是Linux:

在此处输入图片说明

将该文件放置在守护程序服务文件夹中(通常为/etc/systemd/system/),然后使用以下systemctl命令进行安装(可能需要sudo特权):

systemctl enable <service file name without extension>

systemctl daemon-reload

systemctl start <service file name without extension>

然后可以使用以下命令检查服务是否正在运行:

systemctl | grep running
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.