Como faço para daemonizar um script arbitrário no Unix?

93

Eu gostaria de um daemonizador que pudesse transformar um script ou comando genérico arbitrário em um daemon .

Existem dois casos comuns com os quais eu gostaria de lidar:

  1. Tenho um script que deve ser executado para sempre. Se ele morrer (ou na reinicialização), reinicie-o. Não deixe que haja duas cópias em execução ao mesmo tempo (detecte se uma cópia já está sendo executada e não a execute nesse caso).

  2. Tenho um script simples ou comando de linha de comando que gostaria de executar repetidamente para sempre (com uma pequena pausa entre as execuções). Novamente, não permita que duas cópias do script sejam executadas ao mesmo tempo.

Claro que é trivial escrever um loop "while (true)" em torno do script no caso 2 e, em seguida, aplicar uma solução para o caso 1, mas uma solução mais geral resolverá apenas o caso 2 diretamente, uma vez que se aplica ao script no caso 1 como bem (você pode apenas querer um mais curto ou sem pausa se o script não se destina a morrer nunca (é claro que se o script realmente não nunca morrem, em seguida, a pausa na verdade não importa)).

Observe que a solução não deve envolver, digamos, a adição de código de bloqueio de arquivo ou gravação PID aos scripts existentes.

Mais especificamente, gostaria de um programa "daemonize" que posso executar como

% daemonize myscript arg1 arg2

ou, por exemplo,

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

que manteria uma lista crescente de datas anexada a times.txt. (Observe que se o (s) argumento (s) para daemonize for um script que executa para sempre como no caso 1 acima, então daemonize ainda fará a coisa certa, reiniciando-o quando necessário.) Eu poderia então colocar um comando como o acima em meu .login e / ou cron-lo de hora em hora ou minuto (dependendo de quão preocupado eu estava sobre ele morrer inesperadamente).

NB: O script daemonize precisará lembrar a string de comando que está daemonizando para que, se a mesma string de comando for daemonizada novamente, não execute uma segunda cópia.

Além disso, a solução deve funcionar idealmente no OS X e no Linux, mas soluções para um ou outro são bem-vindas.

EDIT: Tudo bem se você tiver que invocá-lo com sudo daemonize myscript myargs.

(Se estou pensando nisso tudo errado ou há soluções parciais rápidas e sujas, adoraria ouvir isso também.)


PS: Caso seja útil, aqui está uma pergunta semelhante específica para python.

E esta resposta a uma pergunta semelhante tem o que parece ser uma expressão útil para uma demonização rápida e suja de um script arbitrário:

Dreeves
fonte
1
Consulte serverfault.com/questions/311593/… para uma versão shell pura
w00t

Respostas:

90

Você pode daemonizar qualquer executável no Unix usando nohup e o operador &:

nohup yourScript.sh script args&

O comando nohup permite que você desligue sua sessão de shell sem matar seu script, enquanto o & coloca seu script em segundo plano para que você obtenha um prompt de shell para continuar sua sessão. O único problema menor com isso é a saída padrão e o erro padrão, ambos enviados para ./nohup.out, portanto, se você iniciar vários scripts nesta mansão, a saída deles será interligada. Um comando melhor seria:

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

Isso enviará o padrão para o arquivo de sua escolha e o erro padrão para um arquivo diferente de sua escolha. Se quiser usar apenas um arquivo para saída padrão e erro padrão, você pode usar o seguinte:

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

O 2> & 1 diz ao shell para redirecionar o erro padrão (descritor de arquivo 2) para o mesmo arquivo de saída padrão (descritor de arquivo 1).

Para executar um comando apenas uma vez e reiniciá-lo se ele morrer, você pode usar este script:

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

O primeiro argumento é o nome do arquivo pid a ser usado. O segundo argumento é o comando. E todos os outros argumentos são os argumentos do comando.

Se você nomear esse script de restart.sh, é assim que você o chamaria:

nohup restart.sh pidFileName yourScript.sh script args >script.out 2>&1 &
Robert Menteer
fonte
Impressionante; obrigado. Gostaria de saber se também deveria haver uma opção para um atraso no reinício. Ou talvez melhor apenas usá-lo em conjunto com isto: stackoverflow.com/questions/555116/…
dreeves
4
Isso lida apenas com SIGHUP, existem outros sinais fatais (normalmente) que devem ser tratados.
Tim Post
Outra maneira de melhorar este script é provavelmente fazer com que ele encontre um bom local para colocar o $ PIDFILE sozinho, em vez de exigir que ele seja especificado como um arg. Ele nem mesmo limpa depois de si mesmo! (o que deve ser direto com a trap EXIT)
Steven Lu
Além disso, considere que o uso de <no testé uma comparação ASCII e não uma comparação inteira. Ainda pode funcionar, mas pode levar a bugs.
Steven Lu
Publiquei minhas correções para este script aqui .
Steven Lu
33

Peço desculpas pela longa resposta (por favor, veja os comentários sobre como minha resposta acerta as especificações). Estou tentando ser abrangente, para que você tenha uma boa vantagem. :-)

Se você é capaz de instalar programas (tem acesso root), e está disposto a fazer um trabalho braçal único para configurar seu script para a execução do daemon (ou seja, mais envolvido do que simplesmente especificar os argumentos da linha de comando para executar na linha de comando, mas só precisa ser feito uma vez por serviço), eu tenho uma maneira que é mais robusta.

Envolve o uso de daemontools . O restante da postagem descreve como configurar serviços usando daemontools.

Configuração inicial

  1. Siga as instruções em Como instalar daemontools . Algumas distribuições (por exemplo, Debian, Ubuntu) já possuem pacotes para ele, então apenas use-os.
  2. Faça um diretório chamado /service. O instalador já deve ter feito isso, mas é só verificar, ou se estiver instalando manualmente. Se você não gostar desse local, poderá alterá-lo em seu svscanbootscript, embora a maioria dos usuários de daemontools esteja acostumada a usar /servicee ficará confuso se você não usá-lo.
  3. Se estiver usando o Ubuntu ou outra distro que não usa padrão init(ou seja, não usa /etc/inittab), você precisará usar o pré-instalado inittabcomo base para organizar svscanboota chamada de init. Não é difícil, mas você precisa saber como configurar o initque seu SO usa. svscanbooté um script que chama svscan, que faz o trabalho principal de procura de serviços; é chamado de initentão initprovidenciará para reiniciá-lo se ele morrer por qualquer motivo.

Configuração por serviço

  1. Cada serviço precisa de um diretório de serviço , que armazena informações de manutenção sobre o serviço. Você também pode criar um local para hospedar esses diretórios de serviço, de modo que todos estejam em um só lugar; normalmente eu uso /var/lib/svscan, mas qualquer novo local vai servir.
  2. Eu costumo usar um script para configurar o diretório de serviço, para economizar muito trabalho manual repetitivo. por exemplo,

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

    onde some-service-nameé o nome que você deseja dar ao seu serviço, useré o usuário para executar esse serviço e loguseré o usuário para executar o logger. (O registro é explicado em breve.)

  3. Seu serviço deve ser executado em primeiro plano . Se o plano de fundo do seu programa for padrão, mas tiver a opção de desativá-lo, faça-o. Se o plano de fundo do seu programa não tiver uma maneira de desativá-lo, continue lendo fghack, embora haja uma troca: você não pode mais controlar o programa usando svc.
  4. Edite o runscript para garantir que ele está fazendo o que você deseja. Pode ser necessário fazer uma sleepchamada na parte superior, se você espera que seu serviço saia com frequência.
  5. Quando tudo estiver configurado corretamente, crie um link simbólico /serviceapontando para o diretório de serviço. (Não coloque diretórios de serviço diretamente dentro /service; isso torna mais difícil remover o serviço do svscanwatch.)

Exploração madeireira

  1. A maneira daemontools de registro é fazer com que o serviço grave mensagens de registro na saída padrão (ou erro padrão, se você estiver usando scripts gerados com mkservice); svscancuida do envio de mensagens de log para o serviço de registro.
  2. O serviço de registro obtém as mensagens de registro da entrada padrão. O script de serviço de registro gerado por mkservicecriará arquivos de registro com registro de data e hora e rotação automática no log/maindiretório. O arquivo de log atual é chamado current.
  3. O serviço de registro pode ser iniciado e interrompido independentemente do serviço principal.
  4. Canalizar os arquivos de log tai64nlocaltraduzirá os carimbos de data / hora em um formato legível por humanos. (TAI64N é um carimbo de data / hora atômico de 64 bits com uma contagem de nanossegundos.)

Serviços de controle

  1. Use svstatpara obter o status de um serviço. Observe que o serviço de registro é independente e tem seu próprio status.
  2. Você controla seu serviço (iniciar, parar, reiniciar, etc.) usando svc. Por exemplo, para reiniciar seu serviço, use svc -t /service/some-service-name; -tsignifica "enviar SIGTERM".
  3. Outros sinais disponíveis incluem -h( SIGHUP), -a( SIGALRM), -1( SIGUSR1), -2( SIGUSR2) e -k( SIGKILL).
  4. Para desativar o serviço, use -d. Você também pode impedir que um serviço seja iniciado automaticamente na inicialização, criando um arquivo nomeado downno diretório do serviço.
  5. Para iniciar o serviço, use -u. Isso não é necessário, a menos que você o tenha baixado anteriormente (ou configurado para não iniciar automaticamente).
  6. Para pedir ao supervisor para sair, use -x; geralmente usado com -dpara encerrar o serviço também. Esta é a maneira usual de permitir que um serviço seja removido, mas você tem que desvincular o serviço /serviceprimeiro, ou então svscanreiniciará o supervisor. Além disso, se você criou seu serviço com um serviço de registro ( mkservice -l), lembre-se de também sair do supervisor de registro (por exemplo, svc -dx /var/lib/svscan/some-service-name/log) antes de remover o diretório de serviço.

Resumo

Prós:

  1. daemontools fornece uma maneira à prova de balas para criar e gerenciar serviços. Eu o uso para meus servidores e recomendo muito.
  2. Seu sistema de registro é muito robusto, assim como o recurso de reinicialização automática do serviço.
  3. Como ele inicia os serviços com um script de shell que você escreve / ajusta, você pode personalizar seu serviço como quiser.
  4. Ferramentas poderosas de controle de serviço: você pode enviar praticamente qualquer sinal para um serviço e pode ativar e desativar os serviços de maneira confiável.
  5. Seus serviços têm a garantia de um ambiente de execução limpo: eles serão executados com o mesmo ambiente, limites de processo, etc., conforme initfornecido.

Contras:

  1. Cada serviço requer um pouco de configuração. Felizmente, isso só precisa ser feito uma vez por serviço.
  2. Os serviços devem ser configurados para serem executados em primeiro plano. Além disso, para obter melhores resultados, eles devem ser configurados para registrar a saída padrão / erro padrão, em vez de syslog ou outros arquivos.
  3. Curva de aprendizado íngreme se você for novo no modo de fazer as coisas daemontools. Você deve reiniciar os serviços usando svce não pode executar os scripts de execução diretamente (já que eles não estariam sob o controle do supervisor).
  4. Muitos arquivos de manutenção e muitos processos de manutenção. Cada serviço precisa de seu próprio diretório de serviço e cada serviço usa um processo de supervisor para reiniciar automaticamente o serviço se ele morrer. (Se você tem muitos serviços, você vai ver lotes de superviseprocessos em sua mesa processo.)

Em suma, acho que o daemontools é um sistema excelente para as suas necessidades. Agradeço qualquer pergunta sobre como configurá-lo e mantê-lo.

Chris Jester-Young
fonte
Como minha resposta acerta as especificações: 1. Você deve configurar serviços, portanto, desde que não configure duplicatas (e desde que seu serviço não tenha um segundo plano), nenhuma duplicação ocorrerá. 2. supervise, o supervisor, se encarrega de reiniciar qualquer serviço que saia. Espera um segundo entre os reinícios; se isso não for tempo suficiente para você, coloque em suspensão no início do script de execução de serviço.
Chris Jester-Young,
2a. superviseé apoiado por ele mesmo svscan, portanto, se um supervisor morrer, ele será reiniciado. 2b. svscané apoiado por init, que será reiniciado automaticamente svscanconforme necessário. 2c. Se você initmorrer por qualquer motivo, você está ferrado de qualquer maneira. :-P
Chris Jester-Young,
Para responder a outras perguntas sobre manutenção, o sistema daemontools não usa arquivos PID, pois eles podem ficar desatualizados. Em vez disso, todas as informações do processo são mantidas pelo supervisor de um determinado serviço. O supervisor mantém vários arquivos (e FIFOs) no diretório de serviço que as ferramentas gostam svstate svcpodem trabalhar.
Chris Jester-Young,
3
Devíamos ter mais posts como esse no SO e na rede em geral. Não apenas uma receita de como conseguir o efeito desejado, mas aquela que se dá ao trabalho de explicar as receitas. Por que não posso votar a favor mais de uma vez? : |
skytreader
12

Eu acho que você pode querer tentar start-stop-daemon(8). Verifique os scripts em /etc/init.dqualquer distribuição Linux para exemplos. Ele pode localizar processos iniciados por linha de comando chamada ou arquivo PID, portanto, atende a todos os seus requisitos, exceto ser um watchdog para seu script. Mas você sempre pode iniciar outro script de watchdog daemon que apenas reinicia seu script, se necessário.

Alex B
fonte
Ainda não existe um daemon start-stop no Fedora, então os scripts que dependem dele não são portáveis. Veja: fedoraproject.org/wiki/Features/start-stop-daemon
Bengt
Apenas um aviso para os usuários do OSX: também não start-stop-daemon(a partir de 10.9).
mklement0
@ mklement0 Bem ... muita coisa mudou em quase 5 anos.
Alex B
Que coisa, como o tempo voa. start-stop-daemonainda está vivo e funcionando no Linux; mas depois de ler resposta stackoverflow.com/a/525406/45375 eu percebi que a OSX faz sua própria coisa: launchd.
mklement0
12

Você deve dar uma olhada no daemonize . Permite detectar a segunda cópia (mas usa mecanismo de bloqueio de arquivo). Também funciona em diferentes distribuições UNIX e Linux.

Se você precisa iniciar automaticamente seu aplicativo como daemon, você precisa criar o script de inicialização apropriado.

Você pode usar o seguinte modelo:

#!/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
uthark
fonte
1
Parece um candidato à resposta correta. Especialmente considerando sua "verificação de instância única".
Martin Wickman
Esta pode ser a melhor resposta possível - não tenho certeza - mas se você acha que é, você também pode incluir uma explicação de por que a especificação que dei na pergunta está errada?
dreeves,
Não gosto do killprocna parte de parada: se você teve um processo que, digamos, foi executado java, o killprocfará com que todos os outros processos Java sejam encerrados também.
Chris Jester-Young,
1
Em /etc/rc.d/init.d/functions, daemonize apenas inicia o binário a partir de um novo shell: $ cgroup $ nice / bin / bash -c $corelimit >/dev/null 2>&1 ; $*Portanto, duvido que vá daemonizar qualquer coisa ...
mbonnin
1
Eu sei que isso é antigo, mas para quem descobrir isso mais tarde ... está correto. "daemon" conforme definido em /etc/init.d/functions não faz daemon para você. É apenas um wrapper para fazer cgroups, verificar se o processo já está rodando, definir um usuário, definir valores nice e ulimit, etc. Ele não daemoniza o processo para você. Esse ainda é o seu trabalho. :)
jakem
7

Como alternativa ao já mencionado daemonizee daemontools, existe o comando daemon do pacote libslack.

daemon é bastante configurável e se preocupa com todas as coisas tediosas daemon, como reinicialização automática, registro ou manipulação de pidfile.

jefz
fonte
5

Se você estiver usando o OS X especificamente, sugiro que dê uma olhada em como funciona o launchd. Ele verificará automaticamente se o script está em execução e o reiniciará, se necessário. Também inclui todos os tipos de recursos de agendamento, etc. Deve atender aos requisitos 1 e 2.

Para garantir que apenas uma cópia do seu script possa ser executada, você precisa usar um arquivo PID. Geralmente, escrevo um arquivo em /var/run/.pid que contém um PID da instância em execução atual. se o arquivo existir quando o programa for executado, ele verifica se o PID no arquivo está realmente em execução (o programa pode ter travado ou esquecido de excluir o arquivo PID). Se for, aborte. Caso contrário, comece a executar e sobrescreva o arquivo PID.

Kamil Kisiel
fonte
5

Daemontools ( http://cr.yp.to/daemontools.html ) é um conjunto de utilitários bastante hard-core usados ​​para fazer isso, escrito por dj bernstein. Eu usei isso com algum sucesso. A parte irritante disso é que nenhum dos scripts retorna qualquer resultado visível quando você os executa - apenas códigos de retorno invisíveis. Mas, uma vez em funcionamento, é à prova de balas.

multiplicação rápida
fonte
Sim, eu ia escrever uma entrada que usa daemontools também. Escreverei minha própria postagem, porque espero ser muito mais abrangente com minha resposta e espero obter a recompensa dessa forma. Veremos. :-)
Chris Jester-Young,
3

Obtenha primeiro createDaemon()em http://code.activestate.com/recipes/278731/

Então o código principal:

import subprocess
import sys
import time

createDaemon()

while True:
    subprocess.call(" ".join(sys.argv[1:]),shell=True)
    time.sleep(10)
Douglas Leeder
fonte
Ooh, obrigado! Quer torná-lo um pouco mais geral para que você possa fazer "daemonize foo arg1 arg2", bem como "daemonize 'foo arg1 arg2'"?
dreeves
Ok, agora ele unirá argumentos - entretanto, você terá que alterá-lo se quiser ter espaços dentro de seus argumentos.
Douglas Leeder
Obrigado Douglas! No entanto, há uma grande falha: executar "daemonize foo" duas vezes inicia duas cópias de foo em execução.
dreeves
Você pode adicionar algum código de gravação PID, mas pode ser melhor executar o script apenas uma vez ...
Douglas Leeder
Estou pensando nisso como fundamental para todo o conceito de wrapper "daemonize". (Por exemplo, eu poderia cronometrá-lo a cada hora ou minuto para ter certeza de que estava sempre em execução.) Estou pensando errado nisso? O createDaemon já garante isso de alguma forma? E após a reinicialização?
dreeves
1

Esta é uma versão de trabalho completa com um exemplo que você pode copiar para um diretório vazio e experimentar (após instalar as dependências CPAN, que são Getopt :: Long , File :: Spec , File :: Pid e IPC :: System: : Simples - todos bastante padrão e altamente recomendados para qualquer hacker: você pode instalá-los todos de uma vez com 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);

exemplo.pl:

#!/usr/bin/perl

use strict;
use warnings;

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

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

Agora você pode invocar o exemplo acima com: ./keepAlive.pl --pidfile=pidfile --command=./example.pl 1 2 3 e o arquivo pidfileserá criado e você verá a saída:

Pid <random number here> no longer running; restarting ./example.pl 1 2 3
./example.pl got arguments: 1 2 3
Éter
fonte
Eu acredito que isso não é exatamente o que se especifica, se bem entendi. Em sua solução (obrigado, btw!) O programa que você deseja daemonizar deve ser modificado para gravar seu PID no arquivo PID. Espero um utilitário que possa daemonizar um script arbitrário.
dreeves,
@dreeves: sim, mas existem duas maneiras de contornar isso: 1. o script invocado por keepAlive.pl (por exemplo, exemplo.pl) poderia ser simplesmente um invólucro para executar o programa real ou 2. keepAlive.pl poderia analisar a tabela de processos ativos do sistema (com Proc :: ProcessTable do CPAN) para tentar encontrar o processo relevante e seu pid).
Ether
1

Você também pode experimentar o Monit . Monit é um serviço que monitora e relata outros serviços. Embora seja usado principalmente como uma forma de notificar (por e-mail e sms) sobre problemas de tempo de execução, ele também pode fazer o que a maioria das outras sugestões aqui defendem. Ele pode (re) iniciar e parar programas automaticamente, enviar e-mails, iniciar outros scripts e manter um registro de saída que você pode obter. Além disso, descobri que é fácil de instalar e manter, pois há uma documentação sólida.

Adestin
fonte
1

Você poderia tentar o imortal. É um supervisor de plataforma cruzada * nix (agnóstico do sistema operacional).

Para uma experiência rápida no macOS:

brew install immortal

Caso você esteja usando o FreeBSD a partir do ports ou usando o pkg:

pkg install immortal

Para Linux , baixando os binários pré-compilados ou da fonte: https://immortal.run/source/

Você pode usá-lo assim:

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

Ou por um arquivo YAML de configuração que oferece mais opções, por exemplo:

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

Se quiser manter também a saída de erro padrão em um arquivo separado, você pode usar algo como:

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
fonte
0

Eu fiz uma série de melhorias na outra resposta .

  1. stdout deste script é puramente feito de stdout vindo de seu filho A MENOS que saia devido à detecção de que o comando já está sendo executado
  2. limpa após seu arquivo pid quando encerrado
  3. período de tempo limite configurável opcional (aceita qualquer argumento numérico positivo, envia para sleep )
  4. prompt de uso em -h
  5. execução de comando arbitrário, em vez de execução de comando único. O último arg OR restantes args (se mais de um último arg) são enviados paraeval , então você pode construir qualquer tipo de script de shell como uma string para enviar a este script como um último arg (ou args à direita) para que ele daemonize
  6. comparações de contagem de argumentos feitas com em -ltvez de<

Aqui está o script:

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

Uso:

$ 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

Esteja ciente de que se você executar este script de diretórios diferentes, ele poderá usar arquivos pid diferentes e não detectar nenhuma instância em execução existente. Uma vez que foi projetado para executar e reiniciar comandos efêmeros fornecidos por meio de um argumento, não há como saber se algo já foi iniciado, porque quem pode dizer se é o mesmo comando ou não? Para melhorar a aplicação de executar apenas uma única instância de algo, é necessária uma solução específica para a situação.

Além disso, para que funcione como um daemon adequado, você deve usar (no mínimo) nohup como a outra resposta menciona. Não fiz nenhum esforço para fornecer resiliência aos sinais que o processo pode receber.

Mais um ponto a ser observado é que matar esse script (se ele foi chamado de outro script que foi morto, ou com um sinal) pode não conseguir matar a criança, especialmente se a criança for outro script. Não tenho certeza do porquê, mas parece ser algo relacionado ao modo de evalfuncionamento, o que é misterioso para mim. Portanto, pode ser prudente substituir essa linha por algo que aceite apenas um único comando, como na outra resposta.

Steven Lu
fonte