如何在unix中守护任意脚本?

时间:2009-02-08 06:11:08

标签: perl unix scripting sysadmin daemon

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

我想处理两种常见情况:

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

  2. 我有一个简单的脚本或命令行命令,我希望永远重复执行(在运行之间暂停一段时间)。同样,不要允许一次运行两个脚本副本。

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

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

    更具体地说,我想要一个程序“daemonize”,我可以像

    一样运行
    % daemonize myscript arg1 arg2
    

    或者,例如,

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

    将保留越来越多的日期列表附加到times.txt。 (注意,如果daemonize的参数是一个永远运行的脚本,如上面的情况1那样,那么daemonize仍然会做正确的事情,必要时重新启动它。)然后我可以在我的.login中输入如上所示的命令和/或每小时或每小时一次(取决于我是多么担心它会意外死亡)。

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

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

    编辑:如果你必须用sudo daemonize myscript myargs调用它,那就没问题了。

    (如果我认为这一切都错了,或者有快速和肮脏的局部解决方案,我也很乐意听到这一点。)


    PS:如果它有用,here's是一个特定于python的类似问题。

    this回答一个类似的问题似乎是一个有用的成语,用于快速和肮脏地妖魔化任意脚本:

13 个答案:

答案 0 :(得分:88)

你可以使用nohup和& amp;来守护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 &

答案 1 :(得分:32)

答案 2 :(得分:12)

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

如果您需要自动启动应用程序作为守护程序,则需要创建适当的init-script。

您可以使用以下模板:

#!/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

答案 3 :(得分:11)

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

答案 4 :(得分:7)

作为已经提到的daemonizedaemontools的替代方法,libslack包中有daemon命令。

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

答案 5 :(得分:5)

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

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

答案 6 :(得分:5)

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

答案 7 :(得分:3)

首先从http://code.activestate.com/recipes/278731/

获取createDaemon()

然后是主要代码:

import subprocess
import time

createDaemon()

while True:
    subprocess.call(" ".join(sys.argv[1:]),shell=True)
    time.sleep(10)

答案 8 :(得分:1)

这是一个完整的工作版本,您可以将其复制到空目录中并尝试(在安装CPAN依赖项后,Getopt::LongFile::SpecFile::Pid,和IPC::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);

<强> example.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

答案 9 :(得分:1)

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

答案 10 :(得分:1)

您可以尝试immortal这是一个* nix跨平台(OS无关)主管。

快速试用macOS:

brew install immortal

如果您使用端口中的FreeBSD或使用pkg:

pkg install immortal

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

您可以像这样使用它:

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

或者通过configuration 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

答案 11 :(得分:0)

我对other answer进行了一系列改进。

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

    这是脚本:

    #!/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
    

    请注意,如果从不同的目录运行此脚本,它可能会使用不同的pidfiles,而不会检测任何现有的运行实例。由于它被设计为运行并重新启动通过参数提供的短暂命令,因此无法知道某些内容是否已经启动,因为谁将说明它是否是相同的命令?为了改进仅运行单个实例的强制执行,需要针对该情况的特定解决方案。

    此外,为了使其成为一个合适的守护进程,你必须使用(至少)nohup,因为另一个答案提到。我没有努力为过程可能获得的信号提供任何弹性。

    需要注意的另一点是,杀死这个脚本(如果它是从另一个被杀死或带有信号的脚本调用的话)可能无法杀死孩子,特别是如果孩子还是另一个脚本。我不确定为什么会这样,但它似乎与eval的工作方式有关,这对我来说是神秘的。因此,用一些只接受一个命令的东西替换那一行可能是谨慎的,就像在另一个答案中一样。

答案 12 :(得分: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 & ) &

当父进程离开舞台时,它也不会成为孤儿