¿La mejor manera de hacer un demonio de script de shell?

81

Me pregunto si hay una mejor manera de hacer un demonio que espera algo usando solo sh que:

#! /bin/sh
trap processUserSig SIGUSR1
processUserSig() {
  echo "doing stuff"
}

while true; do
  sleep 1000
done

En particular, me pregunto si hay alguna forma de deshacerse del bucle y aún hacer que la cosa escuche las señales.

Shawn J. Goff
fuente
3
Necesitará un bucle, pero tenga en cuenta que su ejemplo probablemente no funcionará de la forma esperada. El sueño no es un shell incorporado y un SIGUSR1 recibido por el shell no se propaga a los procesos secundarios. Por lo tanto, su manejador de señales no se procesará hasta que finalice la suspensión. Vea mywiki.wooledge.org/SignalTrap#preview , la tercera sección.
Mike S

Respuestas:

48

Utilice la función de demonio de su sistema, como start-stop-daemon .

De lo contrario, sí, tiene que haber un bucle en alguna parte.

Pausado hasta nuevo aviso.
fuente
2
start-stop-daemon fue sorprendentemente conveniente de usar
mpartel
La URL ya no es válida.
Erenon
118

Simplemente ./myscript &poner en segundo plano su script ( ) no lo demonizará. Consulte http://www.faqs.org/faqs/unix-faq/programmer/faq/ , sección 1.7, que describe lo que se necesita para convertirse en un demonio. Debes desconectarlo del terminal para que SIGHUPno lo mate. Puede utilizar un atajo para hacer que un script parezca actuar como un demonio;

nohup ./myscript 0<&- &>/dev/null &

hará el trabajo. O, para capturar stderr y stdout en un archivo:

nohup ./myscript 0<&- &> my.admin.log.file &

Sin embargo, puede haber otros aspectos importantes que debe tener en cuenta. Por ejemplo:

  • Seguirá teniendo un descriptor de archivo abierto para el script, lo que significa que el directorio en el que está montado sería imposible de montar. Para ser un verdadero demonio, debe chdir("/")(o cd /dentro de su script), y bifurcar para que el padre salga y, por lo tanto, el descriptor original se cierre.
  • Quizás corra umask 0. Es posible que no desee depender de la umask del llamador del demonio.

Para un ejemplo de un script que toma todos estos aspectos en cuenta, ver la respuesta de Mike S' .

Ken B
fuente
1
Primero, gracias por esta respuesta. En general, me está funcionando muy bien. PERO me gustaría agregar al archivo de registro y cuando intento "& >> log.txt" obtengo este error ... "error de sintaxis cerca del token inesperado`> '"¿alguna idea para mí?
tom stratton
7
¿Qué 0<&-hacer? No es obvio lo que logra esa secuencia de personajes.
Craig McQueen
13
No es obvio lo que 0<&-se pretende hacer. Encontré este enlace que lo explica.
Craig McQueen
2
Eso es correcto, 0<&-cierra stdin (fd 0). De esa manera, si su proceso lee accidentalmente desde stdin (fácil de hacer), obtendrá un error en lugar de quedarse para siempre esperando a que aparezcan los datos.
Bronson
1
nohup redirige stdin desde / dev / null automáticamente (consulte el manual). Cerrar descriptores de archivos std no es la mejor práctica.
mecha
74

A algunas de las respuestas votadas aquí les faltan algunas partes importantes de lo que hace que un demonio sea un demonio, en lugar de un proceso en segundo plano o un proceso en segundo plano separado de un shell.

Este http://www.faqs.org/faqs/unix-faq/programmer/faq/ describe lo que se necesita para ser un demonio. Y este script Ejecutar bash como demonio implementa el setid, aunque pierde el chdir a la raíz.

La pregunta del póster original era en realidad más específica que "¿Cómo creo un proceso de demonio usando bash?", Pero dado que el tema y las respuestas discuten la demonización de los scripts de shell en general, creo que es importante señalarlo (para intrusos como yo que investigan el finos detalles de la creación de un demonio).

Aquí está mi interpretación de un script de shell que se comportaría de acuerdo con las preguntas frecuentes. Configure DEBUG en truepara ver una salida bonita (pero también sale inmediatamente en lugar de recorrer un bucle sin fin):

#!/bin/bash
DEBUG=false

# This part is for fun, if you consider shell scripts fun- and I do.
trap process_USR1 SIGUSR1

process_USR1() {
    echo 'Got signal USR1'
    echo 'Did you notice that the signal was acted upon only after the sleep was done'
    echo 'in the while loop? Interesting, yes? Yes.'
    exit 0
}
# End of fun. Now on to the business end of things.

print_debug() {
    whatiam="$1"; tty="$2"
    [[ "$tty" != "not a tty" ]] && {
        echo "" >$tty
        echo "$whatiam, PID $$" >$tty
        ps -o pid,sess,pgid -p $$ >$tty
        tty >$tty
    }
}

me_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
me_FILE=$(basename $0)
cd /

#### CHILD HERE --------------------------------------------------------------------->
if [ "$1" = "child" ] ; then   # 2. We are the child. We need to fork again.
    shift; tty="$1"; shift
    $DEBUG && print_debug "*** CHILD, NEW SESSION, NEW PGID" "$tty"
    umask 0
    $me_DIR/$me_FILE XXrefork_daemonXX "$tty" "$@" </dev/null >/dev/null 2>/dev/null &
    $DEBUG && [[ "$tty" != "not a tty" ]] && echo "CHILD OUT" >$tty
    exit 0
fi

##### ENTRY POINT HERE -------------------------------------------------------------->
if [ "$1" != "XXrefork_daemonXX" ] ; then # 1. This is where the original call starts.
    tty=$(tty)
    $DEBUG && print_debug "*** PARENT" "$tty"
    setsid $me_DIR/$me_FILE child "$tty" "$@" &
    $DEBUG && [[ "$tty" != "not a tty" ]] && echo "PARENT OUT" >$tty
    exit 0
fi

##### RUNS AFTER CHILD FORKS (actually, on Linux, clone()s. See strace -------------->
                               # 3. We have been reforked. Go to work.
exec >/tmp/outfile
exec 2>/tmp/errfile
exec 0</dev/null

shift; tty="$1"; shift

$DEBUG && print_debug "*** DAEMON" "$tty"
                               # The real stuff goes here. To exit, see fun (above)
$DEBUG && [[ "$tty" != "not a tty" ]]  && echo NOT A REAL DAEMON. NOT RUNNING WHILE LOOP. >$tty

$DEBUG || {
while true; do
    echo "Change this loop, so this silly no-op goes away." >/dev/null
    echo "Do something useful with your life, young padawan." >/dev/null
    sleep 10
done
}

$DEBUG && [[ "$tty" != "not a tty" ]] && sleep 3 && echo "DAEMON OUT" >$tty

exit # This may never run. Why is it here then? It's pretty.
     # Kind of like, "The End" at the end of a movie that you
     # already know is over. It's always nice.

La salida se ve así cuando DEBUGse establece en true. Observe cómo cambian los números de ID de grupo de proceso y sesión (SESS, PGID):

<shell_prompt>$ bash blahd

*** PARENT, PID 5180
  PID  SESS  PGID
 5180  1708  5180
/dev/pts/6
PARENT OUT
<shell_prompt>$ 
*** CHILD, NEW SESSION, NEW PGID, PID 5188
  PID  SESS  PGID
 5188  5188  5188
not a tty
CHILD OUT

*** DAEMON, PID 5198
  PID  SESS  PGID
 5198  5188  5188
not a tty
NOT A REAL DAEMON. NOT RUNNING WHILE LOOP.
DAEMON OUT
Mike S
fuente
¡Es una muy buena respuesta! ¿Tú (Mike S) tienes algún tipo de presencia en línea además de SO (blog, red social, lo que sea?) No vi nada como esto en la información de tu perfil.
Michaël Le Barbier
@ MichaelGrünewald No mucho formalmente. Me temo que no soy tan interesante; No blogueo mucho. Soy dueño del dominio schwager.com (que tiene poco en este momento), y tengo algunos proyectos de Arduino. Mi nom de plume es GreyGnome, generalmente. Puedes buscar en Google GreyGnome o GreyGnome + Arduino y encontrarme.
Mike S
1
@MikeS ¡Gracias! Lo pregunto porque no es tan común conocer gente genuinamente interesada en la programación de shell, y siempre estoy feliz de intercambiar ideas y puntos de vista sobre esto. ¡Gracias por esa información complementaria! :)
Michaël Le Barbier
@MikeS, ¿por qué necesitas bifurcar dos veces? En su código, el nieto del script principal se convierte en un nuevo líder de sesión con setsid, ¿no? ¿Por qué no puedes hacer esto en la primera bifurcación?
promaty
De las preguntas frecuentes a las que hice referencia en mi publicación: "Estos son los pasos para convertirse en un demonio: ... 1. fork()' so the parent can exit, this returns control to the command line or shell invoking your program. ... 2. setsid () 'para convertirse en un grupo de proceso y líder de grupo de sesión ... nuestro proceso ahora no tiene una terminal de control, que es algo bueno para los demonios ... 3. `fork () 'de nuevo para que el padre ... pueda salir. Esto significa que nosotros, como líderes de grupo que no son de sesión, nunca podremos recuperar una terminal controladora".
Mike S
63
# double background your script to have it detach from the tty
# cf. http://www.linux-mag.com/id/5981 
(./program.sh &) & 
carlo
fuente
¡Buen truco! Por lo general, voy sin ningún problema, pero definitivamente encontraré uso para este.
joel
¡Excelente! No sé si es la forma correcta, pero funciona de maravilla.
Marc MAURICE
1
Eso no parece desconexión stdin, stdout, stderr. Al menos no con sh.
Craig McQueen
3
Este método para despegar se usa bastante. Se llama "bifurcación doble" y se explica con más profundidad en una de las sagradas escrituras de UNIX, 2nd Stevens ( amazon.com/dp/0201433079 ).
Dave
1
Para obtener más información técnica sobre la bifurcación doble y setsid (), consulte thelinuxjedi.blogspot.com/2014/02/… . Lea especialmente la sección "El desglose", donde dice: "Los pasos reales detrás de la bifurcación doble son los siguientes:"
Mike S
4

Realmente depende de lo que haga el binario en sí.

Por ejemplo, quiero crear un oyente.

El demonio inicial es una tarea sencilla:

lis_deamon:

#!/bin/bash

# We will start the listener as Deamon process
#    
LISTENER_BIN=/tmp/deamon_test/listener
test -x $LISTENER_BIN || exit 5
PIDFILE=/tmp/deamon_test/listener.pid

case "$1" in
      start)
            echo -n "Starting Listener Deamon .... "
            startproc -f -p $PIDFILE $LISTENER_BIN
            echo "running"
            ;;
          *)
            echo "Usage: $0 start"
            exit 1
            ;;
esac

así es como iniciamos el demonio (forma común para todo el personal de /etc/init.d/)

ahora en cuanto al oyente en sí, debe ser algún tipo de bucle / alerta o de lo contrario activará el script para hacer lo que quieras. Por ejemplo, si desea que su guión duerma 10 minutos y se despierte y le pregunte cómo está, lo hará con el

while true ; do sleep 600 ; echo "How are u ? " ; done

Aquí está el oyente simple que puede hacer que escuchará sus comandos desde una máquina remota y los ejecutará en local:

oyente:

#!/bin/bash

# Starting listener on some port
# we will run it as deamon and we will send commands to it.
#
IP=$(hostname --ip-address)
PORT=1024
FILE=/tmp/backpipe
count=0
while [ -a $FILE ] ; do #If file exis I assume that it used by other program
  FILE=$FILE.$count
  count=$(($count + 1))
done

# Now we know that such file do not exist,
# U can write down in deamon it self the remove for those files
# or in different part of program

mknod $FILE p

while true ; do 
  netcat -l -s $IP -p $PORT < $FILE |/bin/bash > $FILE
done
rm $FILE

Entonces para comenzar: / tmp / deamon_test / listener start

y para enviar comandos desde el shell (o ajustarlo al script):

test_host#netcat 10.184.200.22 1024
uptime
 20:01pm  up 21 days  5:10,  44 users,  load average: 0.62, 0.61, 0.60
date
Tue Jan 28 20:02:00 IST 2014
 punt! (Cntrl+C)

Espero que esto ayude.

Artem
fuente
2

$ ( cd /; umask 0; setsid your_script.sh </dev/null &>/dev/null & ) &

Congbin Guo
fuente
1

Si tuviera un script.shy quisiera ejecutarlo desde bash y dejarlo ejecutándose incluso cuando quiera cerrar mi sesión de bash, entonces combinaría nohupy &al final.

ejemplo: nohup ./script.sh < inputFile.txt > ./logFile 2>&1 &

inputFile.txtpuede ser cualquier archivo. Si su archivo no tiene entrada, usualmente usamos /dev/null. Entonces el comando sería:

nohup ./script.sh < /dev/null > ./logFile 2>&1 &

Después de eso, cierre su sesión de bash, abra otra terminal y ejecute: ps -aux | egrep "script.sh"y verá que su script todavía se está ejecutando en segundo plano. Por supuesto, si desea detenerlo, ejecute el mismo comando (ps) ykill -9 <PID-OF-YOUR-SCRIPT>

sin nombre
fuente
0

Consulte el proyecto Bash Service Manager : https://github.com/reduardo7/bash-service-manager

Ejemplo de implementación

#!/usr/bin/env bash

export PID_FILE_PATH="/tmp/my-service.pid"
export LOG_FILE_PATH="/tmp/my-service.log"
export LOG_ERROR_FILE_PATH="/tmp/my-service.error.log"

. ./services.sh

run-script() {
  local action="$1" # Action

  while true; do
    echo "@@@ Running action '${action}'"
    echo foo
    echo bar >&2

    [ "$action" = "run" ] && return 0
    sleep 5
    [ "$action" = "debug" ] && exit 25
  done
}

before-start() {
  local action="$1" # Action

  echo "* Starting with $action"
}

after-finish() {
  local action="$1" # Action
  local serviceExitCode=$2 # Service exit code

  echo "* Finish with $action. Exit code: $serviceExitCode"
}

action="$1"
serviceName="Example Service"

serviceMenu "$action" "$serviceName" run-script "$workDir" before-start after-finish

Ejemplo de uso

$ ./example-service
# Actions: [start|stop|restart|status|run|debug|tail(-[log|error])]

$ ./example-service start
# Starting Example Service service...

$ ./example-service status
# Serive Example Service is runnig with PID 5599

$ ./example-service stop
# Stopping Example Service...

$ ./example-service status
# Service Example Service is not running
Eduardo Cuomo
fuente
0

Como muchas respuestas, esta no es una demonización "real" sino más bien una alternativa al nohupenfoque.

echo "script.sh" | at now

Obviamente, existen diferencias con el uso nohup. Para empezar, no hay separación de los padres en primer lugar. Además, "script.sh" no hereda el entorno de los padres.

De ninguna manera esta es una mejor alternativa. Es simplemente una forma diferente (y algo perezosa) de iniciar procesos en segundo plano.

PD: personalmente voté a favor de la respuesta de carlo, ya que parece ser la más elegante y funciona tanto desde la terminal como desde los scripts internos

oᴉɹǝɥɔ
fuente
0

Aquí está el cambio mínimo a la propuesta original para crear un demonio válido en Bourne shell (o Bash):

#!/bin/sh
if [ "$1" != "__forked__" ]; then
    setsid "$0" __forked__ "$@" &
    exit
else
    shift
fi

trap 'siguser1=true' SIGUSR1
trap 'echo "Clean up and exit"; kill $sleep_pid; exit' SIGTERM
exec > outfile
exec 2> errfile
exec 0< /dev/null

while true; do
    (sleep 30000000 &>/dev/null) &
    sleep_pid=$!
    wait
    kill $sleep_pid &>/dev/null
    if [ -n "$siguser1" ]; then
        siguser1=''
        echo "Wait was interrupted by SIGUSR1, do things here."
    fi
done

Explicación:

  • Línea 2-7: Un daemon debe estar bifurcado para que no tenga un padre. Usar un argumento artificial para evitar una bifurcación sin fin. "setsid" se separa del proceso de inicio y del terminal.
  • Línea 9: Nuestra señal deseada debe diferenciarse de otras señales.
  • Línea 10: Se requiere limpieza para deshacerse de los procesos de "suspensión" que cuelgan.
  • Línea 11-13: Redirigir stdout, stderr y stdin del script.
  • Línea 16: dormir de fondo
  • Línea 18: espera espera el final del sueño, pero es interrumpido por (algunas) señales.
  • Línea 19: Elimine el proceso de suspensión, porque todavía se está ejecutando cuando se detecta la señal.
  • Línea 22: Haga el trabajo si SIGUSR1 ha sido atrapado.

Supongo que no hay nada más simple que eso.

Achim
fuente
-2

intente ejecutar usando & si guarda este archivo como program.sh

puedes usar

$. program.sh &
jasimmk
fuente