¿Cómo demonizo un script arbitrario en unix?

93

Me gustaría un daemonizer que pueda convertir un script o comando genérico y arbitrario en un daemon .

Hay dos casos comunes con los que me gustaría tratar:

  1. Tengo un script que debería ejecutarse para siempre. Si alguna vez muere (o al reiniciar), reinícielo. No permita que nunca haya dos copias ejecutándose a la vez (detecte si una copia ya se está ejecutando y no la inicie en ese caso).

  2. Tengo un script simple o un comando de línea de comando que me gustaría seguir ejecutando repetidamente para siempre (con una breve pausa entre ejecuciones). Nuevamente, no permita que dos copias del script se ejecuten a la vez.

Por supuesto, es trivial escribir un bucle "while (verdadero)" alrededor del script en el caso 2 y luego aplicar una solución para el caso 1, pero una solución más general simplemente resolverá el caso 2 directamente, ya que se aplica al script en el caso 1 como así (puede que sólo quieren una más corta o ninguna pausa si el guión no está destinado a morir nunca (por supuesto, si el guión realmente no nunca mueren después de la pausa en realidad no importa)).

Tenga en cuenta que la solución no debería implicar, por ejemplo, agregar código de bloqueo de archivos o grabación PID a los scripts existentes.

Más específicamente, me gustaría un programa "demonizar" que pueda ejecutar como

% daemonize myscript arg1 arg2

o, por ejemplo,

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

que mantendría una lista creciente de fechas adjuntas a times.txt. (Tenga en cuenta que si el (los) argumento (s) para demonizar es un script que se ejecuta para siempre como en el caso 1 anterior, entonces demonizar seguirá haciendo lo correcto, reiniciándolo cuando sea necesario). Entonces podría poner un comando como el anterior en mi .login y / o cron cada hora o cada minuto (dependiendo de cuán preocupado estaba de que muriera inesperadamente).

NB: El script daemonize necesitará recordar la cadena de comando que está demonizando para que si la misma cadena de comando es demonizada nuevamente, no lance una segunda copia.

Además, la solución idealmente debería funcionar tanto en OS X como en Linux, pero las soluciones para uno u otro son bienvenidas.

EDITAR: Está bien si tiene que invocarlo con sudo daemonize myscript myargs.

(Si estoy pensando en todo esto mal o si hay soluciones parciales rápidas y sucias, también me encantaría escuchar eso).


PD: En caso de que sea útil, aquí hay una pregunta similar específica para Python.

Y esta respuesta a una pregunta similar tiene lo que parece ser un idioma útil para una demonización rápida y sucia de un guión arbitrario:

dreeves
fuente
1
Consulte serverfault.com/questions/311593/… para obtener una versión de shell pura
w00t

Respuestas:

90

Puede demonizar cualquier ejecutable en Unix usando nohup y el operador &:

nohup yourScript.sh script args&

El comando nohup le permite cerrar su sesión de shell sin que mate su script, mientras que & coloca su script en segundo plano para que obtenga un indicador de shell para continuar su sesión. El único problema menor con esto es la salida estándar y el error estándar se envían a ./nohup.out, por lo que si inicia varios scripts en esta mansión, su salida se entrelazará. Un mejor comando sería:

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

Esto enviará el estándar al archivo de su elección y el error estándar a un archivo diferente de su elección. Si desea usar solo un archivo para la salida estándar y el error estándar, puede usar esto:

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

El 2> & 1 le dice al shell que redirija el error estándar (descriptor de archivo 2) al mismo archivo que la salida estándar (descriptor de archivo 1).

Para ejecutar un comando solo una vez y reiniciarlo si muere, puede 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

El primer argumento es el nombre del archivo pid a utilizar. El segundo argumento es el comando. Y todos los demás argumentos son los argumentos del comando.

Si le asigna un nombre a este script restart.sh, así es como lo llamaría:

nohup restart.sh pidFileName yourScript.sh script args >script.out 2>&1 &
Robert Menteer
fuente
Increíble; gracias. Me pregunto si también debería tener una opción para retrasar el reinicio. O tal vez sea mejor usarlo junto con esto: stackoverflow.com/questions/555116/…
dreeves
4
Esto solo maneja SIGHUP, hay otras señales (normalmente) fatales que deben manejarse.
Tim Post
Otra forma de mejorar este script es probablemente proponiendo una buena ubicación para colocar el $ PIDFILE por sí solo en lugar de requerir que se especifique como un argumento. ¡Ni siquiera se limpia después de sí mismo! (que debería ser sencillo con a trap EXIT)
Steven Lu
Además, considere que el uso de <en testes una comparación ASCII, no una comparación de números enteros. Puede que aún funcione, pero puede provocar errores.
Steven Lu
He publicado mis correcciones a este script aquí .
Steven Lu
34

Pido disculpas por la respuesta larga (consulte los comentarios sobre cómo mi respuesta cumple con las especificaciones). Estoy tratando de ser comprensivo, para que tengas la mejor ventaja posible. :-)

Si puede instalar programas (tiene acceso de root) y está dispuesto a hacer un trabajo de campo único para configurar su script para la ejecución del demonio (es decir, más complicado que simplemente especificar los argumentos de la línea de comandos para ejecutar en la línea de comandos, pero solo debe hacerse una vez por servicio), tengo una forma que es más sólida.

Implica el uso de daemontools . El resto de la publicación describe cómo configurar servicios usando daemontools.

Configuración inicial

  1. Siga las instrucciones en Cómo instalar daemontools . Algunas distribuciones (por ejemplo, Debian, Ubuntu) ya tienen paquetes, así que utilícelo.
  2. Haga un directorio llamado /service. El instalador ya debería haber hecho esto, pero solo verifique, o si está instalando manualmente. Si no le gusta esta ubicación, puede cambiarla en su svscanbootscript, aunque la mayoría de los usuarios de daemontools están acostumbrados a usar /servicey se confundirán si no la usa.
  3. Si está usando Ubuntu u otra distribución que no usa estándar init(es decir, no usa /etc/inittab), necesitará usar el preinstalado inittabcomo base para organizar la svscanbootllamada init. No es difícil, pero necesita saber cómo configurar el initque usa su sistema operativo. svscanbootes un script que llama svscan, que hace el trabajo principal de buscar servicios; se llama desde, initpor lo que initse encargará de reiniciarlo si muere por cualquier motivo.

Configuración por servicio

  1. Cada servicio necesita un directorio de servicios , que almacena información de limpieza sobre el servicio. También puede crear una ubicación para albergar estos directorios de servicios para que estén todos en un solo lugar; normalmente uso /var/lib/svscan, pero cualquier nueva ubicación estará bien.
  2. Yo suelo usar una secuencia de comandos para configurar el directorio de servicios, para ahorrar una gran cantidad de trabajo manual repetitivo. p.ej,

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

    donde some-service-namees el nombre que desea dar a su servicio, useres el usuario con el que ejecutará ese servicio y loguseres el usuario con el que ejecutará el registrador. (El registro se explica brevemente).

  3. Su servicio debe ejecutarse en primer plano . Si su programa tiene fondos de forma predeterminada, pero tiene una opción para desactivarlo, hágalo. Si el programa no tiene una forma de deshabilitarlo, siga leyendo fghack, aunque esto tiene una compensación: ya no puede controlar el uso del programa svc.
  4. Edite la runsecuencia de comandos para asegurarse de que esté haciendo lo que desea. Puede que tenga que colocar una sleepllamada en la parte superior, si espera que su servicio a la salida con frecuencia.
  5. Cuando todo esté configurado correctamente, cree un enlace simbólico que /serviceapunte a su directorio de servicios. (No coloque directorios de servicios directamente dentro /service; hace que sea más difícil eliminar el servicio del svscanreloj)

Inicio sesión

  1. La forma de registro de daemontools es hacer que el servicio escriba mensajes de registro en la salida estándar (o error estándar, si está utilizando scripts generados con mkservice); svscanse encarga de enviar mensajes de registro al servicio de registro.
  2. El servicio de registro toma los mensajes de registro de la entrada estándar. El script del servicio de registro generado por mkservicecreará archivos de registro con marca de tiempo y rotación automática en el log/maindirectorio. Se llama al archivo de registro actual current.
  3. El servicio de registro se puede iniciar y detener independientemente del servicio principal.
  4. La canalización de los archivos de registro tai64nlocaltraducirá las marcas de tiempo a un formato legible por humanos. (TAI64N es una marca de tiempo atómica de 64 bits con una cuenta de nanosegundos).

Servicios de control

  1. Úselo svstatpara obtener el estado de un servicio. Tenga en cuenta que el servicio de registro es independiente y tiene su propio estado.
  2. Usted controla su servicio (iniciar, detener, reiniciar, etc.) usando svc. Por ejemplo, para reiniciar su servicio, use svc -t /service/some-service-name; -tsignifica "enviar SIGTERM".
  3. Otras señales disponibles incluyen -h( SIGHUP), -a( SIGALRM), -1( SIGUSR1), -2( SIGUSR2) y -k( SIGKILL).
  4. Para reducir el servicio, utilice -d. También puede evitar que un servicio se inicie automáticamente en el arranque creando un archivo con el nombre downen el directorio de servicios.
  5. Para iniciar el servicio, utilice -u. Esto no es necesario a menos que lo haya bajado previamente (o lo haya configurado para que no se inicie automáticamente).
  6. Para pedirle al supervisor que salga, use -x; también se utiliza normalmente con -dpara finalizar el servicio. Esta es la forma habitual de permitir la eliminación de un servicio, pero /serviceprimero debe desvincular el servicio o, de lo contrario svscan, reiniciará el supervisor. Además, si creó su servicio con un servicio de registro ( mkservice -l), recuerde también salir del supervisor de registro (por ejemplo, svc -dx /var/lib/svscan/some-service-name/log) antes de eliminar el directorio de servicios.

Resumen

Pros:

  1. daemontools proporciona una forma infalible de crear y gestionar servicios. Lo uso para mis servidores y lo recomiendo mucho.
  2. Su sistema de registro es muy robusto, al igual que la función de reinicio automático del servicio.
  3. Debido a que inicia los servicios con un script de shell que usted escribe / ajusta, puede personalizar su servicio como desee.
  4. Potentes herramientas de control de servicios: puede enviar casi cualquier señal a un servicio y puede activar y desactivar servicios de manera confiable.
  5. Sus servicios tienen garantizado un entorno de ejecución limpio: se ejecutarán con el mismo entorno, límites de proceso, etc., que lo initproporciona.

Contras:

  1. Cada servicio requiere un poco de configuración. Afortunadamente, esto solo debe hacerse una vez por servicio.
  2. Los servicios deben configurarse para ejecutarse en primer plano. Además, para obtener mejores resultados, deben configurarse para que se registren en la salida estándar / error estándar, en lugar de syslog u otros archivos.
  3. Curva de aprendizaje pronunciada si es nuevo en la forma de hacer las cosas de daemontools. Debe reiniciar los servicios utilizando svcy no puede ejecutar los scripts de ejecución directamente (ya que entonces no estarían bajo el control del supervisor).
  4. Muchos archivos de limpieza y muchos procesos de limpieza. Cada servicio necesita su propio directorio de servicios y cada servicio utiliza un proceso de supervisor para reiniciar automáticamente el servicio si muere. (Si usted tiene muchos servicios, verá una gran cantidad de superviseprocesos en su tabla de procesos.)

En resumen, creo que daemontools es un sistema excelente para sus necesidades. Agradezco cualquier pregunta sobre cómo configurarlo y mantenerlo.

Chris Jester-Young
fuente
Cómo mi respuesta clava la especificación: 1. Tienes que configurar los servicios, así que mientras no configures duplicados (y mientras tu servicio no tenga un segundo plano), no se producirán duplicados. 2. supervise, el supervisor, se encarga de reiniciar cualquier servicio que salga. Espera un segundo entre reinicios; si no es suficiente tiempo para usted, ponga en suspensión en la parte superior del script de ejecución del servicio.
Chris Jester-Young
2a. superviseestá respaldado por sí mismo svscan, por lo que si un supervisor muere, se reiniciará. 2b. svscanestá respaldado por init, que se reiniciará automáticamente svscansegún sea necesario. 2c. Si initmueres por alguna razón, estás jodido de todos modos. :-P
Chris Jester-Young
Para responder otras preguntas sobre la limpieza, el sistema daemontools no usa archivos PID, ya que pueden volverse obsoletos. En cambio, toda la información del proceso la guarda el supervisor que respalda un servicio determinado. El supervisor mantiene un montón de archivos (y FIFO) en el directorio de servicios que les gustan a las herramientas svstaty con los que svcpueden trabajar.
Chris Jester-Young
3
Deberíamos tener más publicaciones como esta en SO y en la red en general. No solo una receta sobre cómo lograr el efecto deseado, sino una que se toma la molestia de explicar las recetas. ¿Por qué no puedo votar más de una vez? : |
skytreader
12

Creo que quizás quieras intentarlo start-stop-daemon(8). Consulte los scripts en /etc/init.dcualquier distribución de Linux para ver ejemplos. Puede encontrar procesos iniciados mediante la invocación de la línea de comandos o el archivo PID, por lo que cumple con todos sus requisitos, excepto como un perro guardián para su script. Pero siempre puede iniciar otro script de vigilancia de demonios que simplemente reinicie su script si es necesario.

Alex B
fuente
No hay un demonio de inicio-parada en Fedora, por lo que los scripts que dependen de él no son portátiles. Ver: fedoraproject.org/wiki/Features/start-stop-daemon
Bengt
Solo un aviso para los usuarios de OSX: no start-stop-daemon, tampoco (a partir de 10.9).
mklement0
@ mklement0 Bueno ... muchas cosas han cambiado en casi 5 años.
Alex B
Dios mío, cómo pasa el tiempo. start-stop-daemonSin embargo, todavía está vivo y coleando en Linux; pero después de leer la respuesta stackoverflow.com/a/525406/45375 me di cuenta que OSX hace su propia cosa: launchd.
mklement0
12

Debería echar un vistazo a daemonize . Permite detectar una segunda copia (pero utiliza un mecanismo de bloqueo de archivos). También funciona en diferentes distribuciones de UNIX y Linux.

Si necesita iniciar automáticamente su aplicación como demonio, entonces necesita crear un script de inicio apropiado.

Puede utilizar la siguiente plantilla:

#!/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
fuente
1
Parece un candidato para la respuesta correcta. Especialmente considerando su "verificación de instancia única".
Martin Wickman
Esta podría ser la mejor respuesta posible, no estoy seguro, pero si cree que lo es, ¿puede incluir también una explicación de por qué la especificación que di en la pregunta es errónea?
llega el
No me gusta lo que está killprocen la parte de detención: si tuvieras un proceso que, digamos, se ejecutó java, también killprocse eliminarán todos los demás procesos de Java.
Chris Jester-Young
1
Desde /etc/rc.d/init.d/functions, daemonize simplemente inicia el binario desde un nuevo shell: $ cgroup $ nice / bin / bash -c $corelimit >/dev/null 2>&1 ; $*Así que dudo que vaya a demonizar algo ...
mbonnin
1
Sé que esto es antiguo, pero para cualquiera que lo encuentre más tarde ... esto es correcto. "daemon" tal como se define en /etc/init.d/functions en realidad no se daemoniza para usted. Es sólo un envoltorio que hacer cgroups, cheque si el proceso ya está en ejecución, configurar un usuario, buen conjunto y los valores de ulimit, etc. No No daemonize el proceso para usted. Sigue siendo tu propio trabajo. :)
jakem
7

Como alternativa a lo ya mencionado daemonizey daemontools, existe el comando daemon del paquete libslack.

daemon es bastante configurable y se preocupa por todas las cosas tediosas del demonio, como el reinicio automático, el registro o el manejo de archivos pid.

jefz
fuente
5

Si estás usando OS X específicamente, te sugiero que eches un vistazo a cómo funciona launchd. Verificará automáticamente que su script se esté ejecutando y lo reiniciará si es necesario. También incluye todo tipo de funciones de programación, etc. Debe satisfacer tanto el requisito 1 como el 2.

En cuanto a garantizar que solo se pueda ejecutar una copia de su secuencia de comandos, debe utilizar un archivo PID. Generalmente escribo un archivo en /var/run/.pid que contiene un PID de la instancia en ejecución actual. si el archivo existe cuando se ejecuta el programa, comprueba si el PID del archivo se está ejecutando realmente (es posible que el programa se haya bloqueado o se haya olvidado de eliminar el archivo PID). Si es así, aborta. De lo contrario, comience a ejecutar y sobrescriba el archivo PID.

Kamil Kisiel
fuente
5

Daemontools ( http://cr.yp.to/daemontools.html ) es un conjunto de utilidades bastante complejas que se utilizan para hacer esto, escritas por dj bernstein. He usado esto con cierto éxito. La parte molesta de esto es que ninguno de los scripts devuelve resultados visibles cuando los ejecuta, solo códigos de retorno invisibles. Pero una vez que funciona, es a prueba de balas.

multiplicacion rapida
fuente
Sí, iba a escribir una entrada que también usa daemontools. Escribiré mi propia publicación, porque espero ser mucho más completo con mi respuesta y espero obtener la recompensa de esa manera. Ya veremos. :-)
Chris Jester-Young
3

Primero obtenga createDaemon()de http://code.activestate.com/recipes/278731/

Entonces el 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
fuente
¡Oh, gracias! ¿Quiere hacerlo un poco más general para poder hacer "demonizar foo arg1 arg2" así como "demonizar 'foo arg1 arg2'"?
dreeves
Ok, se unirá a los argumentos ahora; sin embargo, tendrá que cambiarlo si alguna vez desea tener espacios dentro de sus argumentos.
Douglas Leeder
¡Gracias Douglas! Sin embargo, hay un gran defecto: ejecutar "daemonize foo" dos veces inicia dos copias de foo en ejecución.
dreeves
Podría agregar algún código de grabación PID, pero sería mejor ejecutar el script solo una vez ...
Douglas Leeder
Estoy pensando en eso como algo fundamental para todo el concepto de envoltorio "demonizar". (Por ejemplo, podría cronometrarlo cada hora o cada minuto para asegurarme de que siempre se esté ejecutando). ¿Estoy pensando en esto mal? ¿CreateDaemon ya garantiza eso de alguna manera? ¿Qué pasa después del reinicio?
Dreeves
1

Esta es una versión funcional completa con un ejemplo que puede copiar en un directorio vacío y probar (después de instalar las dependencias de CPAN, que son Getopt :: Long , File :: Spec , File :: Pid e IPC :: System: : Simple : todo bastante estándar y muy recomendado para cualquier hacker: puede instalarlos todos a la vez con 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);

ejemplo.pl:

#!/usr/bin/perl

use strict;
use warnings;

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

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

Ahora puede invocar el ejemplo anterior con: ./keepAlive.pl --pidfile=pidfile --command=./example.pl 1 2 3y se pidfilecreará el archivo y verá el resultado:

Pid <random number here> no longer running; restarting ./example.pl 1 2 3
./example.pl got arguments: 1 2 3
Éter
fuente
Creo que esto no es del todo conforme a las especificaciones, si lo entiendo correctamente. En su solución (¡gracias, por cierto!), El programa que desea demonizar tiene que ser modificado para escribir su PID en el archivo PID. Espero una utilidad que pueda demonizar un script arbitrario.
dreeves
@dreeves: sí, pero hay dos formas de evitarlo: 1. el script invocado por keepAlive.pl (por ejemplo, example.pl) podría ser simplemente un contenedor para ejecutar el programa real, o 2. keepAlive.pl podría analizar la tabla de procesos activos del sistema (con Proc :: ProcessTable de CPAN) para intentar encontrar el proceso relevante y su pid).
Ether
1

También puede probar Monit . Monit es un servicio que monitorea e informa sobre otros servicios. Si bien se usa principalmente como una forma de notificar (por correo electrónico y sms) sobre problemas de tiempo de ejecución, también puede hacer lo que la mayoría de las otras sugerencias aquí han recomendado. Puede auto (re) iniciar y detener programas, enviar correos electrónicos, iniciar otros scripts y mantener un registro de salida que puede recoger. Además, descubrí que es fácil de instalar y mantener, ya que hay documentación sólida.

Adestin
fuente
1

Podrías intentar inmortal. Es un supervisor multiplataforma * nix (independiente del sistema operativo).

Para una prueba rápida en macOS:

brew install immortal

En caso de que esté usando FreeBSD desde los puertos o usando pkg:

pkg install immortal

Para Linux descargando los binarios precompilados o desde la fuente: https://immortal.run/source/

Puedes usarlo así:

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

O mediante un archivo YAML de configuración que le brinda más opciones, por ejemplo:

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

Si desea mantener también la salida de error estándar en un archivo separado, puede 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
fuente
0

He realizado una serie de mejoras en la otra respuesta .

  1. stdout de este script se compone puramente de stdout proveniente de su hijo A MENOS QUE salga debido a la detección de que el comando ya se está ejecutando
  2. limpia después de su archivo pid cuando se termina
  3. período de tiempo de espera configurable opcional (acepta cualquier argumento numérico positivo, envía a sleep)
  4. aviso de uso en -h
  5. ejecución de comandos arbitraria, en lugar de ejecución de un solo comando. El último argumento O los argumentos restantes (si se envía más de un último argumento) eval, por lo que puede construir cualquier tipo de script de shell como una cadena para enviar a este script como último argumento (o argumentos finales) para que se demonice
  6. comparaciones de recuento de argumentos realizadas con en -ltlugar de<

Aquí está el guión:

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

Tenga en cuenta que si ejecuta este script desde diferentes directorios, puede usar diferentes archivos pid y no detectar ninguna instancia en ejecución existente. Dado que está diseñado para ejecutar y reiniciar comandos efímeros proporcionados a través de un argumento, no hay forma de saber si algo ya se inició, porque ¿quién puede decir si es el mismo comando o no? Para mejorar esta aplicación de ejecutar solo una instancia de algo, se requiere una solución específica para la situación.

Además, para que funcione como un demonio adecuado, debe usar (como mínimo) nohup como menciona la otra respuesta. No he hecho ningún esfuerzo por proporcionar resistencia a las señales que pueda recibir el proceso.

Un punto más a tener en cuenta es que matar este guión (si fue llamado desde otro guión que se mata, o con una señal) puede no tener éxito en matar al niño, especialmente si el niño es otro guión. No estoy seguro de por qué es así, pero parece ser algo relacionado con la forma en que evalfunciona, lo cual me resulta misterioso. Por lo tanto, puede ser prudente reemplazar esa línea con algo que acepte solo un comando como en la otra respuesta.

Steven Lu
fuente