Как мне демонизировать произвольный скрипт в unix? - PullRequest
91 голосов
/ 08 февраля 2009

Мне нужен демонизатор, который может превратить произвольный общий сценарий или команду в демон .

Есть два распространенных случая, с которыми я бы хотел разобраться:

  1. У меня есть скрипт, который должен работать вечно. Если он когда-либо умрет (или перезагрузится), перезапустите его. Не допускайте одновременного запуска двух копий (определите, запущена ли уже копия и не запускайте ее в этом случае).

  2. У меня есть простой скрипт или команда командной строки, которую я хотел бы продолжать выполнять многократно вечно (с небольшой паузой между запусками). Опять же, не позволяйте одновременно запускать две копии скрипта.

Конечно, тривиально написать цикл while (true) вокруг сценария в случае 2, а затем применить решение для случая 1, но более общее решение просто решит случай 2 напрямую, поскольку это применимо к сценарию в также случай 1 (вы можете просто захотеть сделать более короткую паузу или вообще не делать паузу, если скрипт не предназначен для того, чтобы когда-либо умереть (конечно, если скрипт действительно действительно никогда не умрет, тогда пауза на самом деле не имеет значения)).

Обратите внимание, что решение не должно включать, скажем, добавление кода блокировки файла или запись PID в существующие сценарии.

Точнее, я бы хотел программу "daemonize", которую я мог бы запустить как

% daemonize myscript arg1 arg2

или, например,

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

, который будет содержать растущий список дат, добавленный в times.txt. (Обратите внимание, что если аргумент (ы) для daemonize является сценарием, который выполняется вечно, как в случае 1 выше, то daemonize все равно будет делать правильные вещи, перезапуская его при необходимости.) Затем я мог бы поместить команду, как указано выше, в мой .login и / или cron это ежечасно или ежечасно (в зависимости от того, насколько я волновался, что он внезапно умрет).

NB. Сценарий daemonize должен будет помнить командную строку, которую он демонизирует, чтобы при повторной демонизации той же командной строки он не запускал вторую копию.

Кроме того, решение в идеале должно работать как на OS X, так и на Linux, но решения для одного или другого приветствуются.

РЕДАКТИРОВАТЬ: Это нормально, если вам нужно вызвать его с sudo daemonize myscript myargs.

(Если я думаю об этом все неправильно или есть быстрые и грязные частичные решения, я бы тоже хотел это услышать.)


PS: В случае, если это полезно, вот аналогичный вопрос, специфичный для python.

И этот ответ на аналогичный вопрос имеет то, что представляется полезным для быстрой и грязной демонизации произвольного сценария:

Ответы [ 12 ]

1 голос
/ 18 марта 2010

Это рабочая версия с примером, который можно скопировать в пустой каталог и опробовать (после установки зависимостей CPAN: Getopt :: Long , File :: Spec , File :: 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
0 голосов
/ 07 июля 2013

Я сделал ряд улучшений для другого ответа .

  1. stdout из этого сценария состоит исключительно из stdout, исходящего от его дочернего элемента, ЕСЛИ он не выходит из-за обнаружения, что команда уже выполняется
  2. очищается после pid-файла после завершения
  3. необязательный настраиваемый период ожидания (принимает любой положительный числовой аргумент, отправляет sleep)
  4. запрос на использование -h
  5. выполнение произвольной команды, а не выполнение одной команды. Последний аргумент ИЛИ оставшиеся аргументы (если их более одного) отправляются на eval, поэтому вы можете создать любой сценарий оболочки в виде строки для отправки этому сценарию в качестве последнего аргумента (или конечных аргументов) для него демон
  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

Помните, что если вы запускаете этот скрипт из разных каталогов, он может использовать разные pid-файлы и не обнаруживать существующие запущенные экземпляры. Поскольку он предназначен для запуска и перезапуска эфемерных команд, предоставляемых через аргумент, нет способа узнать, было ли что-то уже запущено, потому что кто скажет, является ли это той же самой командой или нет? Чтобы улучшить это принудительное выполнение только одного экземпляра чего-либо, требуется решение, специфичное для данной ситуации.

Кроме того, для того, чтобы он функционировал как настоящий демон, вы должны использовать (как минимум) nohup, как упоминает другой ответ. Я не приложил никаких усилий, чтобы обеспечить устойчивость сигналов, которые может получить процесс.

Еще один момент, на который следует обратить внимание, заключается в том, что уничтожение этого сценария (если он был вызван из другого сценария, который был убит или с помощью сигнала) может не привести к успешному убийству ребенка, особенно если ребенок еще другой скрипт. Я не уверен, почему это так, но, похоже, что-то связано с тем, как работает eval, что для меня загадочно. Поэтому может быть целесообразно заменить эту строку чем-то, что принимает только одну команду, как в другом ответе.

...