web-dev-qa-db-fra.com

Le meilleur moyen de créer un démon de script shell?

Je me demande s'il existe un meilleur moyen de créer un démon qui attend quelque chose en n'utilisant que sh que:

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

while true; do
  sleep 1000
done

En particulier, je me demande s’il est possible de se débarrasser de la boucle tout en laissant la chose écouter les signaux.

67
Shawn J. Goff

Utilisez la fonction démon de votre système, telle que start-stop-daemon .

Sinon, oui, il doit y avoir une boucle quelque part.

44
Dennis Williamson

Arrière-plan de votre script (./myscript &) ne le démonalisera pas. Voir http://www.faqs.org/faqs/unix-faq/programmer/faq/ , section 1.7, qui décrit ce qui est nécessaire pour devenir un démon. Vous devez le déconnecter du terminal pour que SIGHUP ne le tue pas. Vous pouvez utiliser un raccourci pour donner l’impression qu’un script se comporte comme un démon;

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

va faire le travail. Ou, pour capturer stderr et stdout dans un fichier:

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

Cependant, vous devrez peut-être prendre en compte d’autres aspects importants. Par exemple:

  • Vous aurez toujours un descripteur de fichier ouvert au script, ce qui signifie que le répertoire dans lequel il est monté serait impossible à monter. Pour être un véritable démon, vous devez chdir("/") (ou cd / dans votre script), et branchez-le de manière à ce que le parent quitte, de sorte que le descripteur d'origine soit fermé.
  • Peut-être exécuter umask 0. Vous pouvez ne pas vouloir dépendre de l'umask de l'appelant du démon.

Pour un exemple de script prenant en compte tous ces aspects, voir Mike S 'answer .

106
Ken B
# double background your script to have it detach from the tty
# cf. http://www.linux-mag.com/id/5981 
(./program.sh &) & 
60
carlo

Certaines des réponses votées par le plus de votes ici manquent certaines parties importantes de ce qui fait d’un démon un démon, par opposition à un simple processus en arrière-plan ou à un processus en arrière-plan détaché d’un Shell.

Ce http://www.faqs.org/faqs/unix-faq/programmer/faq/ décrit ce qui est nécessaire pour être un démon. Et this Exécuter le script bash en tant que démon implémente setsid, bien qu'il manque le chdir à root.

La question de l'affiche originale était en réalité plus précise que "Comment créer un processus démon avec bash?", Mais comme le sujet et les réponses traitent de la démonisation des scripts Shell en général, il est important de le préciser (pour les intruseurs détails de la création d’un démon).

Voici mon interprétation d'un script Shell qui se comporterait conformément à la FAQ. Définissez DEBUG sur true pour afficher une jolie sortie (mais elle se ferme immédiatement plutôt que de se répéter en boucle):

#!/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 man." >/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 sortie ressemble à ceci lorsque DEBUG est défini sur true. Remarquez comment les numéros de session et de groupe de processus (SESS, PGID) changent:

<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
60
Mike S

Cela dépend vraiment de ce que le binaire lui-même va faire.

Par exemple, je veux créer un auditeur.

Le démon de départ est une tâche simple:

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

c’est ainsi que nous démarrons le démon (moyen commun à tous les employés de /etc/init.d/)

maintenant, s’agissant de l’auditeur lui-même, Ce doit être une sorte de boucle/alerte ou bien cela déclenchera le script de faire ce que vous voulez. Par exemple, si vous voulez que votre script dorme 10 min .__ et vous réveille et vous demande comment vous allez, vous le ferez avec le 

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

Voici le simple auditeur que vous pouvez faire qui écoutera vos commandes Depuis une machine distante et les exécutera sur local:

auditeur:

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

Donc, pour le démarrer:/tmp/deamon_test/listener start

et pour envoyer des commandes à partir de Shell (ou l'envelopper dans un 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)

J'espère que cela aidera.

4
Artem

Jetez un coup d'œil à l'outil daemon du paquet libslack:

http://ingvar.blog.linpro.no/2009/05/18/todays-sysadmin-tip-using-libslack-daemon-to-daemonize-a-script/

Sous Mac OS X, utilisez un script launchd pour le démon Shell.

1
timo

Si j'avais un script.sh et que je voulais l'exécuter depuis bash et le laisser fonctionner même lorsque je voulais fermer ma session bash, je combinerais Nohup et & à la fin.

exemple: Nohup ./script.sh < inputFile.txt > ./logFile 2>&1 &

inputFile.txt peut être n'importe quel fichier. Si votre fichier n'a aucune entrée, nous utilisons généralement /dev/null. Donc, la commande serait:

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

Après cela fermez votre session bash, ouvrez un autre terminal et exécutez: ps -aux | egrep "script.sh" et vous verrez que votre script est toujours en cours d’exécution en arrière-plan. Bien sûr, si vous voulez l’arrêter, exécutez les mêmes commandes (ps) et kill -9 <PID-OF-YOUR-SCRIPT>

1
noName

Comme beaucoup de réponses, celle-ci n’est pas une "vraie" démonisation, mais plutôt une alternative à l’approche Nohup.

echo "script.sh" | at now

Il existe évidemment des différences avec Nohup. Pour l'un, il n'y a pas de détachement du parent en premier lieu. De plus, "script.sh" n'hérite pas de l'environnement du parent.

En aucun cas c'est une meilleure alternative. C'est simplement une manière différente (et un peu paresseuse) de lancer des processus en arrière-plan.

P.S. Personnellement, j'ai voté en faveur de la réponse de carlo, qui semble être la plus élégante et fonctionne à la fois en mode terminal et en script interne

0
oᴉɹǝɥɔ

Voir le projet Bash Service Manager: https://github.com/reduardo7/bash-service-manager

Exemple d'implémentation

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

Exemple d'utilisation

$ ./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
0
Eduardo Cuomo

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

0
Congbin Guo