特定の電子メール アドレスをチェックし、新しい電子メールを外部プログラムに渡す Python スクリプトを作成しました。このスクリプトを 24 時間年中無休で実行するにはどうすればよいですか (Linux でデーモンまたはサービスに変換するなど)。プログラム内で決して終わらないループも必要ですか、それともコードを複数回再実行するだけで実行できますか?
15 に答える
ここには 2 つのオプションがあります。
スクリプトを呼び出す適切なcron ジョブを作成します。Cron は、設定したスケジュールに従って定期的にスクリプトを起動する GNU/Linux デーモンの一般名です。スクリプトを crontab に追加するか、スクリプトへのシンボリック リンクを特別なディレクトリに配置すると、デーモンがスクリプトをバックグラウンドで起動するジョブを処理します。ウィキペディアで詳細を読むことができます。さまざまな cron デーモンがありますが、GNU/Linux システムには既にインストールされている必要があります。
スクリプトがそれ自体をデーモン化できるようにするには、ある種のPython アプローチ(ライブラリなど) を使用します。はい、単純なイベント ループが必要です (イベントがタイマー トリガーであり、スリープ機能によって提供される可能性があります)。
2. を選択することはお勧めしません。実際には、cron 機能を繰り返すことになるからです。Linux システムのパラダイムは、複数の単純なツールが相互作用して問題を解決できるようにすることです。(定期的にトリガーすることに加えて) デーモンを作成する必要がある追加の理由がない限り、他のアプローチを選択してください。
また、ループでデーモン化を使用してクラッシュが発生した場合、その後は誰もメールをチェックしません (この回答へのコメントでIvan Nevostruevが指摘したように)。スクリプトが cron ジョブとして追加された場合、スクリプトは再びトリガーされます。
ここから取った素敵なクラスがあります:
#!/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().
"""
python-daemonライブラリを使用する必要があります。これがすべてを処理します。
PyPI から:行儀の良い Unix デーモン プロセスを実装するためのライブラリ。
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)
これで始められることを願っています。
ループをバックグラウンド サービスとして 24 時間 365 日実行することが本当に必要であると仮定すると、
コードにライブラリを挿入する必要のないソリューションの場合、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
シェル スクリプトを使用して、Python スクリプトをサービスとして実行することもできます。まずこのように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 スクリプトを開始および停止できます。
シンプルでサポートされているバージョンはDaemonize
.
Python Package Index (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()
cron
多くの目的で優れた選択肢であることは明らかです。ただし、OPで要求したとおりにサービスまたはデーモンを作成しません。 cron
ジョブを定期的に実行するだけで (つまり、ジョブの開始と停止を意味します)、頻度は 1 分に 1 回までです。問題がありますcron
。たとえば、スクリプトの前のインスタンスがまだ実行されていて、次回cron
スケジュールが来て新しいインスタンスが起動された場合、それは問題ありませんか? cron
依存関係を処理しません。スケジュールが指示したときにジョブを開始しようとするだけです。
本当にデーモン (実行を停止しないプロセス) が必要な状況を見つけた場合は、 を参照してくださいsupervisord
。通常のデーモン化されていないスクリプトまたはプログラムをラップし、デーモンのように動作させる簡単な方法を提供します。これは、ネイティブの Python デーモンを作成するよりもはるかに優れた方法です。
$nohup
Linuxでコマンドを使用するのはどうですか?
Bluehost サーバーでコマンドを実行するために使用します。
間違っていたらアドバイスお願いします。
まず、メール エイリアスについて調べます。メールエイリアスは、デーモンやサービスなどをいじる必要なく、メールシステム内でこれを行います。
特定のメールボックスにメール メッセージが送信されるたびに 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
このソリューションをお勧めします。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()
サービスのように実行されているものを作成するには、次のことを使用できます。
最初に行う必要があるのは、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 interface.py --help
システムが提供するサービス マネージャーを使用します。たとえば、Ubuntu ではupstartを使用します。これにより、起動時の開始、クラッシュ時の再起動など、すべての詳細が処理されます。