如何让 Python 脚本在 Linux 中像服务或守护进程一样运行

2024-10-10 09:29:00
admin
原创
81
摘要:问题描述:我编写了一个 Python 脚本,用于检查某个电子邮件地址并将新电子邮件传递给外部程序。如何让此脚本全天候执行,例如将其转换为 Linux 中的守护进程或服务。我是否还需要一个永不结束的循环,或者是否可以通过多次重新执行代码来实现?解决方案 1:这里您有两个选择。创建一个适当的cron 作业来调用您...

问题描述:

我编写了一个 Python 脚本,用于检查某个电子邮件地址并将新电子邮件传递给外部程序。如何让此脚本全天候执行,例如将其转换为 Linux 中的守护进程或服务。我是否还需要一个永不结束的循环,或者是否可以通过多次重新执行代码来实现?


解决方案 1:

这里您有两个选择。

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

  2. 使用某种Python 方法(例如库)使脚本能够守护自身。是的,它将需要一个简单的事件循环(其中事件是计时器触发的,可能由 sleep 函数提供)。

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

此外,如果您使用带有循环的守护进程并发生崩溃,则此后将没有人会检查邮件(正如Ivan Nevostruev在本答案的评论中指出的那样)。而如果将脚本添加为 cron 作业,它将再次触发。

解决方案 2:

这是一个很棒的课程,取自这里:

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

解决方案 3:

假设你确实希望你的循环作为后台服务全天候运行

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

[Unit]
Description = <Your service description here>
After = network.target # Assuming you want to start after network interfaces are made available
 
[Service]
Type = simple
ExecStart = python <Path of the script you want to run>
User = # User to run the script as
Group = # Group to run the script as
Restart = on-failure # Restart when there are errors
SyslogIdentifier = <Name of logs for the service>
RestartSec = 5
TimeoutStartSec = infinity
 
[Install]
WantedBy = multi-user.target # Make it accessible to other users

将该文件放在守护进程服务文件夹(通常/etc/systemd/system/)中的一个*.service文件中,然后使用以下 systemctl 命令进行安装(可能需要 sudo 权限):

systemctl enable <service file name without .service extension>

systemctl daemon-reload

systemctl start <service file name without .service extension>

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

systemctl | grep running

解决方案 4:

您应该使用python-daemon库,它可以处理一切。

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

解决方案 5:

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

希望这可以帮助你开始。

解决方案 6:

一个简单且受支持的版本是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()

解决方案 7:

您还可以使用 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 脚本。

解决方案 8:

Ubuntu 有一种非常简单的方法来管理服务。对于 Python 来说,不同之处在于所有依赖项(包)都必须位于主文件运行所在的同一目录中。

我刚刚设法创建了这样一项服务,为我的客户提供天气信息。步骤:

  • 像平常一样创建你的 Python 应用程序项目。

  • 在本地安装所有依赖项,如:sudo pip3 install package_name -t 。

  • 创建命令行变量并在代码中处理它们(如果需要)

  • 创建服务文件。例如:

  [Unit]
  Description=1Droid Weather meddleware provider

  [Service]
  Restart=always
  User=root
  WorkingDirectory=/home/ubuntu/weather
  ExecStart=/usr/bin/python3 /home/ubuntu/weather/main.py httpport=9570  provider=OWMap

  [Install]
  WantedBy=multi-user.target
  • 将文件保存为 myweather.service(例如)

  • 确保您的应用在当前目录中启动时运行

  python3  main.py httpport=9570  provider=OWMap
  • 上面生成的服务文件名为 myweather.service(扩展名必须为 .service),系统会将其视为您的服务名称。您将使用该名称与您的服务进行交互。

  • 复制服务文件:

  sudo cp myweather.service /lib/systemd/system/myweather.service
  • 刷新恶魔注册表:

  sudo systemctl daemon-reload
  • 停止服务(如果正在运行)

  sudo service myweather stop
  • 启动服务:

  sudo service myweather start
  • 检查状态(包含打印语句的日志文件):

  tail -f /var/log/syslog
  • 或者使用以下命令检查状态:

  sudo service myweather status
  • 如果需要,返回开始进行另一次迭代

此服务现在正在运行,即使您注销也不会受到影响。是的,如果主机关闭并重新启动,此服务将重新启动...

解决方案 9:

cron显然是许多用途的绝佳选择。但是,它不会像您在 OP 中请求的那样创建服务或守护进程。它 cron只是定期运行作业(即作业启动和停止),并且每分钟不超过一次。存在一些问题——例如,如果下次计划到来并启动新实例 cron时,脚本的先前实例仍在运行,这样可以吗?它不处理依赖关系;它只是在计划要求时尝试启动作业。cron`cron`

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

解决方案 10:

$nohup在Linux上如何使用命令?

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

如果我错了,请指出。

解决方案 11:

如果您正在使用终端(ssh 或其他),并且想要在退出终端后保持长时间脚本继续运行,您可以尝试以下操作:

screen

apt-get install screen

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

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

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

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

因为这个keep_sending_mails.py的 PID 是虚拟屏幕的子进程,而不是终端(ssh)

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

解决方案 12:

首先,了解邮件别名。邮件别名将在邮件系统内完成此操作,而无需您摆弄守护程序或服务或任何类似的东西。

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

解决方案 13:

我推荐这个解决方案。您需要继承并覆盖方法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()

解决方案 14:

要创建像服务一样运行的东西,你可以使用这个东西:

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

应用程序的命令行界面:

接口.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 接口.py--帮助

解决方案 15:

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

解决方案 16:

您可以将进程作为脚本中的子进程或另一个脚本中的子进程来运行,如下所示

subprocess.Popen(arguments, close_fds=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)

或者使用现成的实用程序

https://github.com/megashchik/d-handler

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用