Как мне демонизировать произвольный скрипт в unix?

94

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

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

  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.

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

Dreeves
источник
1
См. Serverfault.com/questions/311593/… для чистой версии оболочки
w00t

Ответы:

93

Вы можете демонизировать любой исполняемый файл в Unix, используя nohup и оператор &:

nohup yourScript.sh script args&

Команда nohup позволяет завершить сеанс оболочки, не убивая сценарий, в то время как & помещает сценарий в фоновый режим, чтобы вы получили приглашение оболочки для продолжения сеанса. Единственная небольшая проблема с этим - стандартный выход и стандартная ошибка, которые отправляются в ./nohup.out, поэтому, если вы запустите несколько сценариев в этом поместье, их вывод будет переплетен. Лучшая команда была бы:

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

Стандартное сообщение будет отправлено в выбранный вами файл, а стандартная ошибка - в другой файл по вашему выбору. Если вы хотите использовать только один файл как для стандартных ошибок, так и для стандартных ошибок, вы можете использовать это:

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

2> & 1 указывает оболочке перенаправить стандартную ошибку (дескриптор файла 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 &
Роберт Ментир
источник
Потрясающие; Спасибо. Интересно, должна ли у него также быть возможность задержки перезапуска. Или, может быть, лучше просто использовать его вместе с этим: stackoverflow.com/questions/555116/…
dreeves
4
Это обрабатывает только SIGHUP, есть и другие (обычно) фатальные сигналы, которые следует обрабатывать.
Тим Пост
Другой способ улучшить этот сценарий - это, вероятно, найти подходящее место для размещения $ PIDFILE самостоятельно, а не требовать его указания в качестве аргумента. Он даже не убирает за собой! (что должно быть прямо с a trap EXIT)
Стивен Лу
Также учтите, что использование <в test- это сравнение ASCII, а не целочисленное сравнение. Он может работать, но может приводить к ошибкам.
Стивен Лу
Я отправил свои исправления в этот сценарий здесь .
Стивен Лу
34

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

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

Это предполагает использование daemontools . Остальная часть сообщения описывает, как настроить службы с помощью daemontools.

Начальная настройка

  1. Следуйте инструкциям в разделе Как установить daemontools . В некоторых дистрибутивах (например, Debian, Ubuntu) уже есть пакеты для него, так что просто используйте их.
  2. Сделайте каталог с именем /service. Установщик уже должен был это сделать, но просто проверьте, или при установке вручную. Если вам не нравится это место, вы можете изменить его в своем svscanbootскрипте, хотя большинство пользователей daemontools привыкли к нему /serviceи запутаются, если вы его не используете.
  3. Если вы используете Ubuntu или другой дистрибутив , который не использует стандартный init(т.е. не используется /etc/inittab), вам нужно будет использовать предварительно установленной в inittabкачестве основы для организации , svscanbootчтобы его называли init. Это несложно, но вам нужно знать, как настроить то, initчто использует ваша ОС. svscanbootэто скрипт, который вызывает svscan, который выполняет основную работу по поиску услуг; он вызывается из, initпоэтому initбудет организован его перезапуск, если он умрет по какой-либо причине.

Настройка для каждой услуги

  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. Поскольку он запускает службы с помощью сценария оболочки, который вы пишете / настраиваете, вы можете настроить свою службу так, как вам нравится.
  4. Мощные инструменты управления сервисами: вы можете послать сервису практически любой сигнал и можете надежно запускать и останавливать сервисы.
  5. Вашим сервисам гарантируется чистая среда выполнения: они будут выполняться с той же средой, ограничениями процессов и т. Д., Что и initпредоставляемые.

Минусы:

  1. Каждая служба требует небольшой настройки. К счастью, это нужно делать только один раз для каждой службы.
  2. Службы должны быть настроены для работы на переднем плане. Кроме того, для достижения наилучших результатов они должны быть настроены для ведения стандартного вывода / стандартной ошибки, а не системного журнала или других файлов.
  3. Крутая кривая обучения, если вы новичок в том, как работает daemontools. Вы должны перезапустить службы с помощью svcи не можете запускать сценарии запуска напрямую (так как в этом случае они не будут находиться под контролем супервизора).
  4. Множество служебных файлов и множество служебных процессов. Каждой службе нужен собственный каталог служб, и каждая служба использует один процесс супервизора для автоматического перезапуска службы в случае ее смерти. (Если у вас есть много услуг, вы увидите много из superviseпроцессов в вашей таблице процессов.)

В целом, я считаю, что daemontools - отличная система для ваших нужд. Я приветствую любые вопросы о том, как его настроить и поддерживать.

Крис Джестер-Янг
источник
Как мой ответ определяет спецификацию: 1. Вам необходимо настроить службы, поэтому, пока вы не настраиваете дубликаты (и пока ваша служба не работает в фоновом режиме), дубликаты не возникают. 2. superviseСупервизор заботится о перезапуске любой завершающейся службы. Он ждет одну секунду между перезапусками; если вам этого недостаточно, переведите в спящий режим в верхней части сценария запуска службы.
Крис Джестер-Янг,
2а. superviseсам поддерживается svscan, поэтому в случае смерти супервизора он будет перезапущен. 2b. svscanподдерживается init, который будет автоматически перезапускаться svscanпри необходимости. 2c. Если ваш initумрет по какой-либо причине, вы все равно облажались. :-P
Крис Джестер-Янг
Чтобы ответить на другие вопросы об обслуживании, система daemontools не использует файлы PID, поскольку они могут устареть. Вместо этого вся информация о процессе хранится у супервизора, поддерживающего данную услугу. Супервизор поддерживает множество файлов (и FIFO) в служебном каталоге, которые нравятся инструментам svstatи svcс которыми они могут работать.
Крис Джестер-Янг,
3
У нас должно быть больше подобных сообщений в SO и в сети в целом. Не просто рецепт достижения желаемого эффекта, но и объяснение рецептов. Почему я не могу проголосовать за это более одного раза? : |
skytreader
12

Думаю, вы можете попробовать start-stop-daemon(8). Просмотрите сценарии в /etc/init.dлюбом дистрибутиве Linux для примеров. Он может находить запущенные процессы по вызванной командной строке или PID-файлу, поэтому он соответствует всем вашим требованиям, кроме функции сторожевого таймера для вашего скрипта. Но вы всегда можете запустить другой скрипт сторожевого пса демона, который при необходимости просто перезапустит ваш скрипт.

Алекс Б
источник
В Fedora пока нет start-stop-daemon, поэтому сценарии, зависящие от него, не переносимы. См .: fedoraproject.org/wiki/Features/start-stop-daemon
Бенгт,
Просто предупреждение для пользователей OSX: start-stop-daemonтам тоже нет (по состоянию на 10.9).
mklement0 04
@ mklement0 Ну ... за почти 5 лет многое изменилось.
Alex B
Ой, как летит время. start-stop-daemonтем не менее, все еще жив и здоров в Linux; но после прочтения ответа stackoverflow.com/a/525406/45375 я понял , что OSX делает свое дело: launchd.
mklement0 04
12

Вам следует взглянуть на 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
Утарк
источник
1
Похоже, кандидат на правильный ответ. Особенно учитывая его «единичную проверку».
Мартин Викман
Это может быть лучший ответ - я не уверен, - но если вы так думаете, можете ли вы также включить объяснение, почему спецификация, которую я дал в вопросе, неверна?
dreeves
Мне не нравится killprocостановка: если у вас есть процесс, который, скажем, запущен java, то killprocвсе остальные процессы Java тоже будут убиты.
Крис Джестер-Янг,
1
Из /etc/rc.d/init.d/functions daemonize просто запускает двоичный файл из новой оболочки: $ cgroup $ nice / bin / bash -c $corelimit >/dev/null 2>&1 ; $*Так что я сомневаюсь, что он что-то демонизирует ...
mbonnin
1
Я знаю, что это старый, но для тех, кто найдет это позже ... это правильно. «демон», как определено в /etc/init.d/functions, на самом деле не выполняет демонизацию для вас. Это просто оболочка для создания cgroups, проверки того, запущен ли процесс, установки пользователя, установки значений nice и ulimit и т. Д. Это не демонизирует процесс для вас. Это все еще твоя собственная работа. :)
jakem
7

В качестве альтернативы уже упомянутому daemonizeи daemontoolsсуществует демон из пакета libslack.

daemon довольно настраиваемый и заботится обо всех утомительных вещах демона, таких как автоматический перезапуск, ведение журнала или обработка pidfile.

Jefz
источник
5

Если вы используете конкретно OS X, я предлагаю вам взглянуть на то, как работает launchd. Он автоматически проверит, работает ли ваш скрипт, и при необходимости перезапустит его. Он также включает в себя всевозможные функции планирования и т. Д. Он должен удовлетворять требованиям 1 и 2.

Что касается обеспечения возможности запуска только одной копии вашего скрипта, вам необходимо использовать файл PID. Обычно я записываю файл в /var/run/.pid, который содержит PID текущего запущенного экземпляра. если файл существует при запуске программы, она проверяет, действительно ли работает PID в файле (программа могла аварийно завершить работу или иным образом забыла удалить файл PID). Если это так, отменить. Если нет, запустите и перезапишите файл PID.

Камиль Кисиэль
источник
5

Daemontools ( http://cr.yp.to/daemontools.html ) - это набор довольно жестких утилит, используемых для этого, написанный dj bernstein. Я использовал это с некоторым успехом. Раздражает то, что ни один из скриптов не возвращает видимых результатов, когда вы их запускаете - только невидимые коды возврата. Но когда он запущен, он пуленепробиваемый.

быстрое умножение
источник
Да, я собирался написать запись, в которой тоже используются daemontools. Я напишу свой собственный пост, потому что надеюсь дать более исчерпывающий ответ, и надеюсь получить награду таким образом. Посмотрим. :-)
Крис Джестер-Янг
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)
Дуглас Лидер
источник
О, спасибо! Хотите сделать его немного более общим, чтобы вы могли выполнять «daemonize foo arg1 arg2», а также «daemonize 'foo arg1 arg2'»?
dreeves,
Хорошо, теперь он присоединится к аргументам - однако вам придется его изменить, если вы когда-нибудь захотите иметь пробелы внутри своих аргументов.
Дуглас Лидер,
Спасибо, Дуглас! Однако есть большой недостаток: запуск "daemonize foo" дважды запускает две копии foo.
dreeves
Вы можете добавить код записи PID, но лучше всего запустить скрипт только один раз ...
Дуглас Лидер,
Я как бы считаю это фундаментальным для всей концепции оболочки "демонизация". (Например, я мог бы затем cron его ежечасно или ежеминутно, чтобы убедиться, что он всегда работает.) Я думаю об этом неправильно? Гарантирует ли createDaemon это каким-то образом? А что после перезагрузки?
dreeves
1

Это рабочая версия с примером, который вы можете скопировать в пустой каталог и попробовать (после установки зависимостей 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
Эфир
источник
Я считаю, что это не совсем так, если я правильно понимаю. В вашем решении (спасибо, кстати!) Программа, которую вы хотите демонизировать, должна быть изменена, чтобы записать свой PID в файл PID. Я надеюсь на утилиту, которая сможет демонизировать произвольный скрипт.
dreeves
@dreeves: да, но есть два способа обойти это: 1. сценарий, вызываемый keepAlive.pl (например, example.pl), может быть просто оболочкой для выполнения реальной программы, или 2. keepAlive.pl может анализировать таблицу активные системные процессы (с помощью Proc :: ProcessTable CPAN), чтобы попытаться найти соответствующий процесс и его pid).
Ether
1

Вы также можете попробовать Monit . Мониторинг - это сервис, который отслеживает и сообщает о других сервисах. Хотя он в основном используется как способ уведомления (по электронной почте и sms) о проблемах во время выполнения, он также может делать то, что предлагается в большинстве других предложений здесь. Он может автоматически (повторно) запускать и останавливать программы, отправлять электронные письма, запускать другие сценарии и вести журнал вывода, который вы можете получить. Кроме того, я обнаружил, что его легко установить и поддерживать, поскольку есть надежная документация.

Адестин
источник
1

Вы могли бы попробовать бессмертный Это * nix кроссплатформенный (независимый от ОС) супервизор.

Чтобы быстро попробовать macOS:

brew install immortal

Если вы используете FreeBSD из портов или с помощью pkg:

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
nbari
источник
0

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

  1. stdout из этого скрипта состоит исключительно из stdout, исходящего от его дочернего элемента, ЕСЛИ он не завершился из-за обнаружения того, что команда уже выполняется
  2. очищается после своего pidfile при завершении
  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работает, что для меня загадочно. Поэтому может быть разумным заменить эту строку чем-то, что принимает только одну команду, как в другом ответе.

Стивен Лу
источник