【问题标题】:Daemonizing a python script in debian在 debian 中守护一个 python 脚本
【发布时间】:2012-12-06 20:34:35
【问题描述】:

我有一个 python 脚本,我想在启动时在后台运行。这是脚本:

#!/usr/bin/python
from Adafruit_CharLCD import Adafruit_CharLCD
from subprocess import * 
from time import sleep, strftime
from datetime import datetime
from datetime import timedelta
from os import system
from os import getloadavg
from glob import glob

#Variables
lcd = Adafruit_CharLCD() #Stores LCD object
cmdIP = "ip addr show eth0 | grep inet | awk '{print $2}' | cut -d/ -f1" #Current IP
cmdHD = "df -h / | awk '{print $5}'" # Available hd space
cmdSD = "df -h /dev/sda1 | awk '{print $5}'" # Available sd space
cmdRam = "free -h"
temp = 0

#Run shell command
def run_cmd(cmd):
    p = Popen(cmd, shell=True, stdout=PIPE)
    output = p.communicate()[0]
    return output

#Initalises temp device     
def initialise_temp():
    #Initialise
    system("sudo modprobe w1-gpio")
    system("sudo modprobe w1-therm")
    #Find device
    devicedir = glob("/sys/bus/w1/devices/28-*")
    device = devicedir[0]+"/w1_slave"
    return device

#Gets temp  
def get_temp(device):
    f = open (device, 'r')
    sensor = f.readlines()
    f.close()

    #parse results from the file
    crc=sensor[0].split()[-1]
    temp=float(sensor[1].split()[-1].strip('t='))
    temp_C=(temp/1000.000)
    temp_F = ( temp_C * 9.0 / 5.0 ) + 32

    #output
    return temp_C

#Gets time
def get_time():
    return datetime.now().strftime('%b %d  %H:%M:%S\n')

#Gets uptime
def get_uptime():
    with open('/proc/uptime', 'r') as f:
        seconds = float(f.readline().split()[0])
        array = str(timedelta(seconds = seconds)).split('.')
        string = array[0].split(' ')
        totalString = string[0] + ":" + string[2]
    return totalString

#Gets average load
def get_load():
    array = getloadavg()
    average = 0
    for i in array:
        average += i
    average = average / 3
    average = average * 100
    average = "%.2f" % average
    return str(average + "%")

#def get_ram():
def get_ram():
    ram = run_cmd(cmdRam)
    strippedRam = ram.replace("\n"," ");
    splitRam = strippedRam.split(' ')
    totalRam = int(splitRam[52].rstrip("M"))
    usedRam = int(splitRam[59].rstrip("M"))
    percentage = "%.2f" % ((float(usedRam) / float(totalRam)) * 100)
    return percentage + "%"

#Gets the SD usage
def get_sd():
    sd = run_cmd(cmdSD)
    strippedSD = sd.lstrip("Use%\n")
    return strippedSD

#Gets the HD usage
def get_hd():
    hd = run_cmd(cmdSD)
    strippedHD = hd.lstrip("Use%\n")
    return strippedHD

def scroll():
    while(1):
        lcd.scrollDisplayLeft()
        sleep(0.5)

#Uptime and IP
def screen1():
    uptime = get_uptime()
    lcd.message('Uptime %s\n' % (uptime))
    ipaddr = run_cmd(cmdIP)
    lcd.message('IP %s' % (ipaddr))

#Ram and load
def screen2():
    ram = get_ram()
    lcd.message('Ram Used %s\n' % (ram))
    load = get_load()
    lcd.message('Avg Load %s' % (load))

#Temp and time
def screen3():
    time = get_time();
    lcd.message('%s\n' % (time))
    lcd.message('Temp %s' % (temp))

#HD and SD usage
def screen4():
    sd = get_sd()
    lcd.message('SD Used %s\n' % (sd))
    hd = get_hd()
    lcd.message('HD Used %s' % (hd))

#Pause and clear
def screenPause(time):
    sleep(time)
    #In here to reduce lag
    global temp
    temp = str(get_temp(device));
    lcd.clear()
###########################################################################################################

#Initialise
lcd.begin(16,2)
device = initialise_temp()
lcd.clear()

#Testing

#Main loop
while(1):
    screen1()
    screenPause(5)
    screen2()
    screenPause(5)
    screen3()
    screenPause(5)
    screen4()
    screenPause(5)

我知道我可能还没有以写的方式做事,但这是第一次尝试。 我的启动脚本在 /etc/init.d 这是脚本:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          LCD looping
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: LCD daemon
# Description:       This file should be used to construct scripts to be
#                    placed in /etc/init.d.
### END INIT INFO

# Author: Foo Bar <foobar@baz.org>
#
# Please remove the "Author" lines above and replace them
# with your own name if you copy and modify this script.

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Loops the LCD screen through LCD.py"
NAME=startup.py
DAEMON=/home/pi/Programming/LCD/startup.py
DAEMON_ARGS=""
PIDFILE=/var/run/daemonLCD.pid
SCRIPTNAME=/etc/init.d/daemonLCD

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/daemonLCD ] && . /etc/default/daemonLCD

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
                || return 1
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
                $DAEMON_ARGS \
                || return 2
        # Add code here, if necessary, that waits for the process to be ready
        # to handle requests from services started subsequently which depend
        # on this one.  As a last resort, sleep for some time.
}

#
# Function that stops the daemon/service
#
do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
        RETVAL="$?"
        [ "$RETVAL" = 2 ] && return 2
        # Wait for children to finish too if this is a daemon that forks
        # and if the daemon is only ever run from this initscript.
        # If the above conditions are not satisfied then add some other code
        # that waits for the process to drop all resources that could be
        # needed by services started subsequently.  A last resort is to
        # sleep for some time.
        start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
        [ "$?" = 2 ] && return 2
        # Many daemons don't delete their pidfiles when they exit.
        rm -f $PIDFILE
        return "$RETVAL"
#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
        #
        # If the daemon can reload its configuration without
        # restarting (for example, when it is sent a SIGHUP),
        # then implement that here.
        #
        start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
        return 0
}

case "$1" in
  start)
        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  stop)
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  status)
        status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
        ;;
  #reload|force-reload)
        #
        # If do_reload() is not implemented then leave this commented out
        # and leave 'force-reload' as an alias for 'restart'.
        #
        #log_daemon_msg "Reloading $DESC" "$NAME"
        #do_reload
        #log_end_msg $?
        #;;

  restart|force-reload)
        #
        # If the "reload" option is implemented then remove the
        # 'force-reload' alias
        #
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  *)
        #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
        echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
        exit 3
        ;;
esac

:

我想我错过了一些东西,因为当我输入 daemonLCD start 时,它说找不到命令。 任何输入都会很棒。

谢谢

【问题讨论】:

  • 你确定这就是它所说的。更有可能是&lt;FOO&gt; :command not found 或类似的... foo 是什么?
  • pi@raspberrypi /etc/init.d $ daemonLCD start -bash: daemonLCD: command not found
  • hostname -i 将是获取当前 IP 的更简单解决方案

标签: python debian daemon raspberry-pi init.d


【解决方案1】:

假设您将来可能要管理多个守护程序,让我推荐Supervisord。这比编写和管理自己的 init.d 脚本要简单得多。

例如,启动脚本就像在 conf 中包含以下内容一样简单:

[program:myscript]
command=/usr/bin/python /path/to/myscript.py

我使用 init.d 脚本 available here。将其重命名为 supervisord 并将其复制到您的 /etc/init.d/ 然后运行:

sudo update-rc.d supervisord defaults

我相信 init 脚本默认以 root 身份运行 supervisord。如果你愿意,你可以让它作为另一个用户运行。如果孩子们以 root 身份运行,我不是,尽管我假设不是。继续检查,但如果他们不这样做,您可以在调用脚本的 supervisord.conf 中的 python 命令之前粘贴 sudo。

它不运行,(或者如果您希望 supervisord 以非 root 身份运行但仍希望您的脚本以 root 身份运行)您可以允许任何人(或一组用户)运行 python 脚本为root (尽管你应该确定这个脚本不能被 root 以外的任何人编辑)。

使用“sudo visudo”编辑您的 sudoers 文件并将以下内容添加到末尾:

USERS ALL=(ALL) NOPASSWD: /path/to/myscript.py

然后确保在 python 脚本的开头有一个 shebang 并更改命令以省略 python 调用,即:

[program:myscript]
command=sudo /path/to/myscript.py

【讨论】:

  • 嗨,我一直在玩 supervisord。我需要以 root 身份启动我的 python 脚本。我该怎么做?我尝试将 user = root 放在 [program:myscript] 中,但没有任何区别。谢谢
  • 你还设置 supervisord 使用 update-rc.d 在启动时运行吗?如果是这样,我是否打算将我的主管文件从 usr/local/bin 移动到 /etc/init.d?再次感谢
【解决方案2】:

这里有一篇很好的博客文章来解决这个问题:Getting a Python script to run in the background (as a service) on boot

【讨论】:

    【解决方案3】:

    使用来自 djb 的daemontools。它比提供的其他答案容易得多。对于初学者,您可以使用 apt-get 安装守护程序工具,这样您就不必担心从 gist 中获取未知脚本,并且您可以像往常一样通过 debian 获取更新。 daemontools 还负责在服务死亡时重新启动服务并提供日志记录。这里有daemontools和debian的描述:

    http://blog.rtwilson.com/how-to-set-up-a-simple-service-to-run-in-the-background-on-a-linux-machine-using-daemontools/

    djb 的页面 aout daemontools:

    http://cr.yp.to/daemontools.html

    【讨论】:

      【解决方案4】:

      这是 Unix/Linux 新手常犯的典型错误。 /etc/init.d 不在您的路径中,这就是您不能只运行 daemonLCD 的原因。尝试使用完整路径 (/etc/init.d/daemonLCD start) 或前置 ./ (./daemonLCD start)。

      脚本必须是可执行的才能使上述任何一个工作。

      【讨论】:

        【解决方案5】:

        感谢上面的代码。我一直在用它来弄清楚如何在 linux 机器上设置守护进程。

        通过一些调整,我可以让它很好地工作。

        但有些事情让我感到困惑。那就是通过检查 /var/run/myfile.pid 的存在来检查进程是否正在运行

        这只是 pidfile - 不是进程,对吧?

        看看 /lib/lsb/init-functions.status_of_proc

        status_of_proc () {
        local pidfile daemon name status OPTIND
        
        pidfile=
        OPTIND=1
        while getopts p: opt ; do
            case "$opt" in
                p)  pidfile="$OPTARG";;
            esac
        done
        shift $(($OPTIND - 1))
        
        if [ -n "$pidfile" ]; then
            pidfile="-p $pidfile"
        fi
        daemon="$1"
        name="$2"
        
        status="0"
        pidofproc $pidfile $daemon >/dev/null || status="$?"
        if [ "$status" = 0 ]; then
            log_success_msg "$name is running"
            return 0
        elif [ "$status" = 4 ]; then
            log_failure_msg "could not access PID file for $name"
            return $status
        else
            log_failure_msg "$name is not running"
            return $status
        fi
        }
        

        这只是处理访问PID文件的成功或失败。

        现在,我正在构建此守护程序以在小型设备上运行。我发现它正在使用 BusyBox 并且我没有初始化函数 :-( 但我确实有 pidof。

        所以我加了

                log_success_msg "pidof $NAME is $(pidof -x $NAME)" >> $LOGFILE
               log_success_msg "PIDFILE of $NAME is" >> $LOGFILE
               sed -n '1p' < $PIDFILE >> $LOGFILE
        

        并检查了 $LOGFILE 和 lo,发现数字不同。

        我对两个数字都做了 pstree -s -p 和

        pidof 号吐出一棵很短的树,所以它是给根级进程的

        但是 $PIDFILE 编号在一个分支一个分支之后吐出,所以我认为 pstree 找不到进程。

        是的,Joseph Baldwin Roberts 代码中的 do_stop 会杀死这两个进程。但是如果进程以另一种方式被杀死,例如kill -9 12345,$PIDFILE 仍然存在。因此,守护进程会错误地认为进程已经在运行拒绝启动。

        【讨论】:

        • 两个进程号不同的解决方法是在调用start之前将PID号写入$pidfile(eg printf "$(pidof -x $name)" >> $pidfile) -do_start 中的停止守护进程。
        猜你喜欢
        • 2011-12-10
        • 2015-07-05
        • 2014-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-14
        • 1970-01-01
        • 2015-11-02
        相关资源
        最近更新 更多