96

我想要一个可以将任意通用脚本或命令转换为daemon的守护程序。

我想处理两种常见的情况:

  1. 我有一个应该永远运行的脚本。如果它死了(或在重新启动时),请重新启动它。不要让两个副本同时运行(检测一个副本是否已经在运行,在这种情况下不要启动它)。

  2. 我有一个简单的脚本或命令行命令,我想永远重复执行(运行之间有短暂的停顿)。同样,不要让脚本的两个副本同时运行。

当然,在案例 2 中围绕脚本编写一个“while(true)”循环然后为案例 1 应用解决方案是微不足道的,但更通用的解决方案将直接解决案例 2,因为这适用于案例 1 中的脚本好吧(如果脚本不打算死掉,你可能只需要更短的暂停或没有暂停(当然,如果脚本真的永远不会死,那么暂停实际上并不重要))。

请注意,该解决方案不应涉及,例如,将文件锁定代码或 PID 记录添加到现有脚本。

更具体地说,我想要一个可以运行的程序“守护进程”

% daemonize myscript arg1 arg2

或者,例如,

% daemonize 'echo `date` >> /tmp/times.txt'

这将使越来越多的日期列表附加到 times.txt。(请注意,如果 daemonize 的参数是一个像上面的情况 1 一样永远运行的脚本,那么 daemonize 仍然会做正确的事情,在必要时重新启动它。)然后我可以在我的 .login 中放置一个类似上面的命令和/或每小时或每分钟 cron 它(取决于我对它意外死亡的担心程度)。

注意: daemonize 脚本需要记住它正在守护的命令字符串,这样如果相同的命令字符串再次被守护,它就不会启动第二个副本。

此外,理想情况下,该解决方案应该适用于 OS X 和 linux,但欢迎使用其中一种或另一种的解决方案。

编辑:如果您必须使用sudo daemonize myscript myargs.

(如果我认为这一切都错了,或者有快速而肮脏的部分解决方案,我也很想听听。)


PS:如果它有用,这里有一个类似的问题特定于 python。

这个对类似问题的回答似乎是一个有用的习语,可以快速而肮脏地妖魔化任意脚本:

4

13 回答 13

99

您可以使用 nohup 和 & 运算符来守护 Unix 中的任何可执行文件:

nohup yourScript.sh script args&

nohup 命令允许您在不杀死脚本的情况下关闭您的 shell 会话,而 & 将您的脚本置于后台,以便您获得 shell 提示以继续您的会话。唯一的小问题是标准输出和标准错误都被发送到./nohup.out,所以如果你在这个庄园中启动多个脚本,它们的输出将交织在一起。更好的命令是:

nohup yourScript.sh script args >script.out 2>script.error&

这会将标准输出发送到您选择的文件,并将标准错误发送到您选择的不同文件。如果您只想对标准输出和标准错误使用一个文件,您可以使用以下命令:

nohup yourScript.sh script args >script.out 2>&1 &

2>&1 告诉 shell 将标准错误(文件描述符 2)重定向到与标准输出(文件描述符 1)相同的文件。

要仅运行一次命令并在它死后重新启动它,您可以使用此脚本:

#!/bin/bash

if [[ $# < 1 ]]; then
    echo "Name of pid file not given."
    exit
fi

# Get the pid file's name.
PIDFILE=$1
shift

if [[ $# < 1 ]]; then
    echo "No command given."
    exit
fi

echo "Checking pid in file $PIDFILE."

#Check to see if process running.
PID=$(cat $PIDFILE 2>/dev/null)
if [[ $? = 0 ]]; then
    ps -p $PID >/dev/null 2>&1
    if [[ $? = 0 ]]; then
        echo "Command $1 already running."
        exit
    fi
fi

# Write our pid to file.
echo $$ >$PIDFILE

# Get command.
COMMAND=$1
shift

# Run command until we're killed.
while true; do
    $COMMAND "$@"
    sleep 10 # if command dies immediately, don't go into un-ctrl-c-able loop
done

第一个参数是要使用的 pid 文件的名称。第二个参数是命令。所有其他参数都是命令的参数。

如果您将此脚本命名为 restart.sh,您可以这样称呼它:

nohup restart.sh pidFileName yourScript.sh script args >script.out 2>&1 &
于 2010-03-11T08:44:53.467 回答
34

对于冗长的答案,我深表歉意(请参阅有关我的答案如何确定规范的评论)。我试图做到全面,所以你有尽可能好的一面。:-)

如果您能够安装程序(具有 root 访问权限),并且愿意一次性完成设置脚本以执行守护程序(即,比简单地指定要在命令行上运行的命令行参数更复杂,但每次服务只需要完成一次),我有一种更强大的方法。

它涉及使用daemontools。这篇文章的其余部分描述了如何使用 daemontools 设置服务。

初始设置

  1. 按照如何安装 daemontools中的说明进行操作。一些发行版(例如,Debian、Ubuntu)已经有它的软件包,所以就使用它。
  2. 创建一个名为/service. 安装程序应该已经这样做了,但只需验证,或者手动安装。如果你不喜欢这个位置,你可以在你的svscanboot脚本中改变它,尽管大多数 daemontools 用户习惯使用/service并且如果你不使用它会感到困惑。
  3. 如果您使用的是 Ubuntu 或其他不使用标准的发行版(即init不使用. 这并不难,但您需要知道如何配置您的操作系统使用的。 是一个调用 的脚本,它完成了寻找服务的主要工作;如果它因任何原因死亡,它将安排重新启动它。/etc/inittabinittabsvscanbootinitinitsvscanbootsvscaninitinit

按服务设置

  1. 每个服务都需要一个服务目录,其中存储有关服务的内务信息。您还可以创建一个位置来存放这些服务目录,以便它们都在一个地方;通常我使用/var/lib/svscan,但任何新位置都可以。
  2. 我通常使用脚本来设置服务目录,以节省大量手动重复工作。例如,

    sudo mkservice -d /var/lib/svscan/some-service-name -l -u user -L loguser "command line here"
    

    some-service-name您要为服务提供的名称是哪里,user是运行该服务loguser的用户,是运行记录器的用户。(稍后会解释日志记录。)

  3. 您的服务必须在前台运行。如果您的程序默认为背景,但可以选择禁用它,请执行此操作。如果您的程序后台无法禁用它,请继续阅读fghack,尽管这是一个权衡:您不能再使用svc.
  4. 编辑run脚本以确保它正在执行您想要的操作。sleep如果您希望您的服务经常退出,您可能需要在顶部拨打电话。
  5. 一切设置正确后,创建一个/service指向您的服务目录的符号链接。(不要将服务目录直接放在 中/service;这会使从svscan的监视中删除服务变得更加困难。)

日志记录

  1. daemontools 的日志记录方式是让服务将日志消息写入标准输出(或标准错误,如果您使用生成的脚本mkservice);svscan负责将日志消息发送到日志服务。
  2. 日志服务从标准输入中获取日志消息。生成的日志服务脚本mkservice将在目录中创建自动轮换的时间戳日志文件log/main。当前日志文件称为current.
  3. 日志服务可以独立于主服务启动和停止。
  4. 通过管道传输日志文件tai64nlocal会将时间戳转换为人类可读的格式。(TAI64N 是一个 64 位原子时间戳,具有纳秒计数。)

控制服务

  1. 用于svstat获取服务的状态。请注意,日志服务是独立的,并且有自己的状态。
  2. 您可以使用svc. 例如,要重新启动您的服务,请使用svc -t /service/some-service-name; -t意思是“发送SIGTERM”。
  3. 其他可用信号包括-h( SIGHUP)、-a( SIGALRM)、-1( SIGUSR1)、-2( SIGUSR2) 和-k( SIGKILL)。
  4. 要关闭服务,请使用-d. down您还可以通过在服务目录中创建一个名为的文件来防止服务在启动时自动启动。
  5. 要启动服务,请使用-u. 除非您之前已将其关闭(或将其设置为不自动启动),否则这不是必需的。
  6. 要让主管退出,请使用-x; 通常也用于-d终止服务。这是允许删除服务的常用方法,但您必须先取消链接服务/service,否则svscan将重新启动主管。此外,如果您使用日志服务 ( ) 创建了服务,请记住在删除服务目录之前mkservice -l还要退出日志主管(例如,svc -dx /var/lib/svscan/some-service-name/log

概括

优点:

  1. daemontools 提供了一种创建和管理服务的防弹方法。我将它用于我的服务器,我强烈推荐它。
  2. 它的日志系统非常强大,服务自动重启工具也是如此。
  3. 因为它使用您编写/调整的 shell 脚本启动服务,所以您可以根据需要定制服务。
  4. 强大的服务控制工具:您可以向服务发送几乎任何信号,并且可以可靠地启动和关闭服务。
  5. 保证您的服务有一个干净的执行环境:它们将在与所提供的环境、进程限制等相同的环境下执行init

缺点:

  1. 每个服务都需要一些设置。值得庆幸的是,每次服务只需执行一次。
  2. 服务必须设置为在前台运行。此外,为了获得最佳结果,应将它们设置为记录到标准输出/标准错误,而不是 syslog 或其他文件。
  3. 如果您不熟悉 daemontools 的做事方式,学习曲线陡峭。您必须使用 重新启动服务svc,并且不能直接运行运行脚本(因为它们将不受主管的控制)。
  4. 大量的内务管理文件和大量的内务处理流程。每个服务都需要自己的服务目录,每个服务使用一个主管进程在服务死亡时自动重启服务。(如果你有很多服务,你会在你的进程表中看到很多进程。supervise

总的来说,我认为 daemontools 是满足您需求的优秀系统。我欢迎任何关于如何设置和维护它的问题。

于 2010-03-18T04:21:26.757 回答
14

你应该看看daemonize。它允许检测第二个副本(但它使用文件锁定机制)。它也适用于不同的 UNIX 和 Linux 发行版。

如果您需要自动启动您的应用程序作为守护进程,那么您需要创建适当的初始化脚本。

您可以使用以下模板:

#!/bin/sh
#
# mydaemon     This shell script takes care of starting and stopping
#               the <mydaemon>
#

# Source function library
. /etc/rc.d/init.d/functions


# Do preliminary checks here, if any
#### START of preliminary checks #########


##### END of preliminary checks #######


# Handle manual control parameters like start, stop, status, restart, etc.

case "$1" in
  start)
    # Start daemons.

    echo -n $"Starting <mydaemon> daemon: "
    echo
    daemon <mydaemon>
    echo
    ;;

  stop)
    # Stop daemons.
    echo -n $"Shutting down <mydaemon>: "
    killproc <mydaemon>
    echo

    # Do clean-up works here like removing pid files from /var/run, etc.
    ;;
  status)
    status <mydaemon>

    ;;
  restart)
    $0 stop
    $0 start
    ;;

  *)
    echo $"Usage: $0 {start|stop|status|restart}"
    exit 1
esac

exit 0
于 2010-03-17T02:52:10.557 回答
12

我想你可能想试试start-stop-daemon(8)。查看/etc/init.d任何 Linux 发行版中的脚本以获取示例。它可以通过调用的命令行或 PID 文件找到已启动的进程,因此它符合您的所有要求,除了作为脚本的看门狗。但是您总是可以启动另一个守护程序看门狗脚本,在必要时重新启动您的脚本。

于 2009-02-09T01:25:44.127 回答
7

作为已经提到的daemonizeand的替代方案daemontools,还有 libslack 包的daemon命令。

daemon是相当可配置的,并且确实关心所有繁琐的守护进程,例如自动重启、日志记录或 pidfile 处理。

于 2012-06-09T14:46:06.790 回答
5

如果你专门使用 OS X,我建议你看看 launchd 是如何工作的。它将自动检查以确保您的脚本正在运行,并在必要时重新启动它。它还包括各种调度功能等。它应该满足要求1和2。

至于确保您的脚本只能运行一个副本,您需要使用 PID 文件。通常我将一个文件写入 /var/run/.pid ,其中包含当前正在运行的实例的 PID。如果程序运行时文件存在,它会检查文件中的 PID 是否实际运行(程序可能已经崩溃或忘记删除 PID 文件)。如果是,则中止。如果没有,开始运行并覆盖 PID 文件。

于 2009-02-08T08:33:25.983 回答
5

Daemontools ( http://cr.yp.to/daemontools.html ) 是一组用于执行此操作的非常核心的实用程序,由 dj bernstein 编写。我已经成功地使用了它。令人讨厌的部分是,当您运行它们时,没有任何脚本返回任何可见的结果 - 只是不可见的返回码。但是一旦它运行它是防弹的。

于 2010-03-17T05:11:50.143 回答
3

首先createDaemon()http://code.activestate.com/recipes/278731/

然后是主要代码:

import subprocess
import sys
import time

createDaemon()

while True:
    subprocess.call(" ".join(sys.argv[1:]),shell=True)
    time.sleep(10)
于 2009-02-08T09:20:16.433 回答
1

这是一个完整的工作版本,带有一个示例,您可以将其复制到一个空目录并试用(在安装 CPAN 依赖项之后,它们是Getopt::LongFile::SpecFile::PidIPC::System : :Simple —— 所有这些都非常标准,强烈推荐给任何黑客:您可以使用 ) 一次性安装它们cpan <modulename> <modulename> ...


keepAlive.pl:

#!/usr/bin/perl

# Usage:
# 1. put this in your crontab, to run every minute:
#     keepAlive.pl --pidfile=<pidfile> --command=<executable> <arguments>
# 2. put this code somewhere near the beginning of your script,
#    where $pidfile is the same value as used in the cron job above:
#     use File::Pid;
#     File::Pid->new({file => $pidfile})->write;

# if you want to stop your program from restarting, you must first disable the
# cron job, then manually stop your script. There is no need to clean up the
# pidfile; it will be cleaned up automatically when you next call
# keepAlive.pl.

use strict;
use warnings;

use Getopt::Long;
use File::Spec;
use File::Pid;
use IPC::System::Simple qw(system);

my ($pid_file, $command);
GetOptions("pidfile=s"   => \$pid_file,
           "command=s"   => \$command)
    or print "Usage: $0 --pidfile=<pidfile> --command=<executable> <arguments>\n", exit;

my @arguments = @ARGV;

# check if process is still running
my $pid_obj = File::Pid->new({file => $pid_file});

if ($pid_obj->running())
{
    # process is still running; nothing to do!
    exit 0;
}

# no? restart it
print "Pid " . $pid_obj->pid . " no longer running; restarting $command @arguments\n";

system($command, @arguments);

例子.pl:

#!/usr/bin/perl

use strict;
use warnings;

use File::Pid;
File::Pid->new({file => "pidfile"})->write;

print "$0 got arguments: @ARGV\n";

现在您可以使用:调用上面的示例,./keepAlive.pl --pidfile=pidfile --command=./example.pl 1 2 3文件pidfile将被创建,您将看到输出:

Pid <random number here> no longer running; restarting ./example.pl 1 2 3
./example.pl got arguments: 1 2 3
于 2010-03-18T00:48:13.547 回答
1

你也可以试试Monit。Monit 是一项监视和报告其他服务的服务。虽然它主要用作通知(通过电子邮件和短信)有关运行时问题的一种方式,但它也可以执行这里大多数其他建议所提倡的。它可以自动(重新)启动和停止程序、发送电子邮件、启动其他脚本以及维护您可以获取的输出日志。此外,我发现它很容易安装和维护,因为有可靠的文档。

于 2017-04-13T13:51:55.600 回答
1

你可以试试immortal它是一个 *nix 跨平台(与操作系统无关)的主管。

在 macOS 上快速尝试:

brew install immortal

如果您从端口或使用 pkg使用FreeBSD :

pkg install immortal

对于Linux,通过下载预编译的二进制文件或从源代码:https ://immortal.run/source/

您可以像这样使用它:

immortal -l /var/log/date.log date

或者通过配置 YAML文件,它为您提供更多选项,例如:

cmd: date
log:
    file: /var/log/date.log
    age: 86400 # seconds
    num: 7     # int
    size: 1    # MegaBytes
    timestamp: true # will add timesamp to log

如果您还想将标准错误输出保存在单独的文件中,您可以使用以下内容:

cmd: date
log:
    file: /var/log/date.log
    age: 86400 # seconds
    num: 7     # int
    size: 1    # MegaBytes
stderr:
    file: /var/log/date-error.log
    age: 86400 # seconds
    num: 7     # int
    size: 1    # MegaBytes
    timestamp: true # will add timesamp to log
于 2017-09-07T19:07:14.667 回答
0

我对另一个答案进行了一系列改进。

  1. 此脚本中的标准输出完全由来自其子级的标准输出组成,除非它由于检测到该命令已在运行而退出
  2. 终止时在其 pidfile 后清理
  3. 可选的可配置超时期限(接受任何正数参数,发送到sleep
  4. 使用提示开启-h
  5. 任意命令执行,而不是单个命令执行。最后一个 arg 或剩余的 args(如果有多个最后一个 arg)被发送到eval,因此您可以将任何类型的 shell 脚本作为字符串发送到该脚本作为最后一个 arg(或尾随 args),以便它进行守护进程
  6. 参数计数比较用-lt代替<

这是脚本:

#!/bin/sh

# this script builds a mini-daemon, which isn't a real daemon because it
# should die when the owning terminal dies, but what makes it useful is
# that it will restart the command given to it when it completes, with a
# configurable timeout period elapsing before doing so.

if [ "$1" = '-h' ]; then
    echo "timeout defaults to 1 sec.\nUsage: $(basename "$0") sentinel-pidfile [timeout] command [command arg [more command args...]]"
    exit
fi

if [ $# -lt 2 ]; then
    echo "No command given."
    exit
fi

PIDFILE=$1
shift

TIMEOUT=1
if [[ $1 =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
        TIMEOUT=$1
        [ $# -lt 2 ] && echo "No command given (timeout was given)." && exit
        shift
fi

echo "Checking pid in file ${PIDFILE}." >&2

#Check to see if process running.
if [ -f "$PIDFILE" ]; then
    PID=$(< $PIDFILE)
    if [ $? = 0 ]; then
        ps -p $PID >/dev/null 2>&1
        if [ $? = 0 ]; then
            echo "This script is (probably) already running as PID ${PID}."
            exit
        fi
    fi
fi

# Write our pid to file.
echo $$ >$PIDFILE

cleanup() {
        rm $PIDFILE
}
trap cleanup EXIT

# Run command until we're killed.
while true; do
    eval "$@"
    echo "I am $$ and my child has exited; restart in ${TIMEOUT}s" >&2
    sleep $TIMEOUT
done

用法:

$ term-daemonize.sh pidfilefortesting 0.5 'echo abcd | sed s/b/zzz/'
Checking pid in file pidfilefortesting.
azzzcd
I am 79281 and my child has exited; restart in 0.5s
azzzcd
I am 79281 and my child has exited; restart in 0.5s
azzzcd
I am 79281 and my child has exited; restart in 0.5s
^C

$ term-daemonize.sh pidfilefortesting 0.5 'echo abcd | sed s/b/zzz/' 2>/dev/null
azzzcd
azzzcd
azzzcd
^C

请注意,如果您从不同的目录运行此脚本,它可能会使用不同的 pidfile,并且不会检测到任何现有的正在运行的实例。由于它旨在运行和重新启动通过参数提供的临时命令,因此无法知道某事是否已经启动,因为谁能说它是否是相同的命令?为了改进这种仅运行单个实例的强制执行,需要针对具体情况的解决方案。

此外,要使其充当适当的守护程序,您必须(至少)使用 nohup 作为其他答案提到的。我没有努力为过程可能收到的信号提供任何弹性。

还有一点需要注意的是,杀死这个脚本(如果它是从另一个被杀死的脚本调用的,或者有一个信号)可能无法成功杀死孩子,特别是如果孩子是另一个脚本。我不确定这是为什么,但这似乎与eval工作方式有关,这对我来说很神秘。因此,谨慎的做法是将该行替换为仅接受单个命令的内容,例如另一个答案。

于 2013-07-06T20:18:03.480 回答
0

还有一种非常简单的双叉+setsid方法可以将任何脚本与其父进程分离

( setsid my-regular-script arg [arg ...] 1>stdout.log 2>stderr.log & )

setsid是标准util-linux软件包的一部分,自诞生以来就与 linux 一起使用。POSIX这在我知道的任何兼容的 shell 中启动时都有效。

另一种基于双叉的方法甚至不需要任何额外的可执行文件或包,并且完全依赖于POSIX基于 shell

( my-regular-script arg [arg ...] 1>stdout.log 2>stderr.log & ) &

当父进程离开阶段时,它也可以成为孤儿

于 2020-12-29T20:33:44.490 回答