在Debian中将Python脚本变成守护进程

3
我有一个 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”时,它显示“未找到命令”。 非常感谢您的任何建议。
谢谢。

你确定它是这样说的吗?更可能是 <FOO> :command not found 或类似的错误...什么是 foo? - SingleNegationElimination
pi@raspberrypi /etc/init.d $ daemonLCD start -bash: daemonLCD: 找不到命令 - Joseph Roberts
“hostname -i”是获取您当前IP地址的简单解决方案。 - tehmisvh
可能是这个的重复:https://dev59.com/wmvXa4cB1Zd3GeqPLrGv - tvuillemin
5个回答

9
假设您将来可能想管理多个守护进程,让我推荐 Supervisord。它比编写和管理自己的init.d脚本要简单得多。
例如,启动您的脚本只需在配置文件中包含以下内容即可:
[program:myscript]
command=/usr/bin/python /path/to/myscript.py

我使用一个init.d脚本在这里可用。将其重命名为supervisord并复制到/etc/init.d/,然后运行:
sudo update-rc.d supervisord defaults

我认为init脚本默认以root身份运行supervisord。如果您愿意,可以让它降级到其他用户身份运行。我不确定子进程是否以root身份运行,尽管我认为不会。请检查一下,但如果不是,请在调用脚本的supervisord.conf中的python命令前面加上sudo。

如果这样还是无法运行(或者您希望supervisord以非root用户身份运行,但仍希望您的脚本以root身份运行),则可以允许任何人(或一组用户)以root身份运行python脚本(尽管您应该确保此脚本除了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]中,但没有任何区别。谢谢。 - Joseph Roberts
你是否设置了supervisord在启动时使用update-rc.d运行?如果是这样,我是否应该将我的supervisord文件从usr/local/bin移动到/etc/init.d?再次感谢。 - Joseph Roberts

1

1

使用来自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的关于daemontools的页面:

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


0
这是新Unix/Linux用户经常犯的一个错误。 /etc/init.d 不在您的路径中,这就是为什么您不能直接运行 daemonLCD 的原因。尝试使用完整路径(/etc/init.d/daemonLCD start)或在前面加上 ./ (./daemonLCD start)。

无论哪种方法,脚本都需要可执行权限才能正常工作。


0

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

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

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

那只是pid文件 - 而不是进程,对吧?

看一下/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,而我没有init-functions :-( 但是我有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,惊讶地发现数字不同。

我在这两个数字上运行了 pstree -s -p,并且

pidof number 告诉我只有一个很短的树,所以它是根级别的进程

但 $PIDFILE 的数字则连绵不断地向外延伸分支,所以我认为 pstree 找不到该进程。

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


解决两个进程号不同的方法是在 do_start 中调用 start-stop-daemon 之前将 PID 号写入 $pidfile(例如 printf "$(pidof -x $name)" >> $pidfile)。 - mycowan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接