Der beste Weg, um einen Shell-Skript-Daemon zu erstellen?

81

Ich frage mich, ob es einen besseren Weg gibt, einen Daemon zu erstellen, der nur mit sh auf etwas wartet, als:

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

while true; do
  sleep 1000
done

Insbesondere frage ich mich, ob es eine Möglichkeit gibt, die Schleife loszuwerden und das Ding trotzdem auf die Signale warten zu lassen.

Shawn J. Goff
quelle
3
Sie benötigen eine Schleife, beachten Sie jedoch, dass Ihr Beispiel wahrscheinlich nicht die erwartete Leistung erbringen wird. Der Schlaf ist keine eingebaute Shell, und ein von der Shell empfangener SIGUSR1 wird nicht an untergeordnete Prozesse weitergegeben. Daher wird Ihr Signalhandler erst verarbeitet, wenn der Ruhezustand beendet ist. Siehe mywiki.wooledge.org/SignalTrap#preview , 3. Abschnitt.
Mike S

Antworten:

48

Verwenden Sie die Daemon-Funktion Ihres Systems, z. B. Start-Stopp-Daemon .

Ansonsten muss es ja irgendwo eine Schleife geben.

Bis auf weiteres angehalten.
quelle
2
Start-Stop-Daemon war überraschend bequem zu bedienen
mpartel
URL ist nicht mehr gültig.
Erenon
118

Nur ein Hintergrundbild Ihres Skripts ( ./myscript &) wird es nicht dämonisieren. Siehe http://www.faqs.org/faqs/unix-faq/programmer/faq/ , Abschnitt 1.7, in dem beschrieben wird, was erforderlich ist, um ein Daemon zu werden. Sie müssen es vom Terminal trennen, damit SIGHUPes nicht zerstört wird. Sie können eine Verknüpfung verwenden, um ein Skript wie einen Dämon erscheinen zu lassen.

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

wird den Job machen. Oder um sowohl stderr als auch stdout in einer Datei zu erfassen:

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

Es kann jedoch weitere wichtige Aspekte geben, die Sie berücksichtigen müssen. Beispielsweise:

  • Für das Skript ist weiterhin ein Dateideskriptor geöffnet. Dies bedeutet, dass das Verzeichnis, in dem es bereitgestellt wird, nicht bereitgestellt werden kann. Um ein echter Daemon zu sein, sollten Sie chdir("/")(oder cd /in Ihrem Skript) und fork, damit das übergeordnete Element beendet wird und somit der ursprüngliche Deskriptor geschlossen wird.
  • Vielleicht rennen umask 0. Möglicherweise möchten Sie nicht von der Umask des Aufrufers des Dämons abhängig sein.

Ein Beispiel für ein Skript , das alle diese Aspekte berücksichtigt, finden Sie Mike S‘Antwort .

Ken B.
quelle
1
Zunächst einmal vielen Dank für diese Antwort. Es funktioniert meistens sehr gut für mich. ABER ich möchte an die Protokolldatei anhängen und wenn ich "& >> log.txt" versuche, erhalte ich diesen Fehler ... "Syntaxfehler in der Nähe eines unerwarteten Tokens`> '"irgendwelche Ideen für mich?
Tom Stratton
7
Was macht 0<&-das Es ist nicht offensichtlich, was diese Zeichenfolge bewirkt.
Craig McQueen
13
Es ist nicht offensichtlich, was 0<&-zu tun ist. Ich habe diesen Link gefunden, der es erklärt.
Craig McQueen
2
Das ist richtig, 0<&-schließt stdin (fd 0). Auf diese Weise wird ein Fehler angezeigt, wenn Ihr Prozess versehentlich von stdin liest (einfach durchzuführen), anstatt ewig darauf zu warten, dass Daten angezeigt werden.
Bronson
1
nohup leitet stdin automatisch von / dev / null um (siehe Handbuch). Das Schließen von Standarddateideskriptoren ist nicht die beste Vorgehensweise.
Docht
73

In einigen der oben aufgeführten Antworten fehlen einige wichtige Teile dessen, was einen Daemon zu einem Daemon macht, im Gegensatz zu nur einem Hintergrundprozess oder einem von einer Shell getrennten Hintergrundprozess.

Diese http://www.faqs.org/faqs/unix-faq/programmer/faq/ beschreibt, was notwendig ist, um ein Daemon zu sein. Und dieses Run-Bash-Skript als Daemon implementiert die Setsid, obwohl das chdir zum Rooten fehlt.

Die Frage des Originalplakats war tatsächlich spezifischer als "Wie erstelle ich einen Daemon-Prozess mit Bash?", Aber da das Thema und die Antworten die Dämonisierung von Shell-Skripten im Allgemeinen behandeln, halte ich es für wichtig, darauf hinzuweisen (für Eindringlinge wie mich, die sich mit dem beschäftigen feine Details zum Erstellen eines Daemons).

Hier ist meine Wiedergabe eines Shell-Skripts, das sich gemäß den häufig gestellten Fragen verhält. Stellen Sie DEBUG auf ein true, um eine hübsche Ausgabe zu sehen (aber es wird auch sofort beendet, anstatt sich endlos zu wiederholen):

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

Die Ausgabe sieht so aus, wenn sie auf eingestellt DEBUGist true. Beachten Sie, wie sich die Sitzungs- und Prozessgruppen-ID-Nummern (SESS, PGID) ändern:

<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.
quelle
Es ist eine sehr schöne Antwort! Haben Sie (Mike S) außer SO (Blog, soziales Netzwerk, was auch immer) irgendeine Art von Online-Präsenz? Ich habe so etwas in Ihren Profilinformationen nicht gesehen.
Michael Le Barbier Grünewald
@ MichaelGrünewald Nicht viel formal. Ich fürchte, ich bin nicht so interessant. Ich blogge nicht viel. Ich besitze die Domain schwager.com (die im Moment wenig hat) und ich habe einige Arduino-Projekte. Mein Nom de Plume ist im Allgemeinen GreyGnome. Sie können Google GreyGnome oder GreyGnome + Arduino und mich finden.
Mike S
1
@ MikeS Danke! Ich frage, weil es nicht so üblich ist, Leute zu treffen, die wirklich an Shell-Programmierung interessiert sind, und ich bin immer gerne bereit, Ideen und Ansichten darüber auszutauschen. Vielen Dank für diese ergänzenden Informationen! :)
Michael Le Barbier Grünewald
@ MikeS, warum musst du zweimal gabeln? In Ihrem Code wird Enkel des übergeordneten Skripts ein neuer Sitzungsleiter mit setsid, ja? Warum kannst du das nicht einfach für die erste Gabel machen?
Promaty
In den FAQ, auf die ich in meinem Beitrag verwiesen habe: "Hier sind die Schritte, um ein Daemon zu werden: ... 1. fork()' so the parent can exit, this returns control to the command line or shell invoking your program. ... 2. setsid () ', um eine Prozessgruppe und ein Sitzungsgruppenleiter zu werden ... unser Prozess hat jetzt kein steuerndes Terminal eine gute Sache für Dämonen ... 3. `fork () 'wieder, damit die Eltern ... beenden können. Dies bedeutet, dass wir als Gruppenleiter außerhalb der Sitzung niemals ein kontrollierendes Terminal wiedererlangen können."
Mike S
62
# double background your script to have it detach from the tty
# cf. http://www.linux-mag.com/id/5981 
(./program.sh &) & 
carlo
quelle
Cooler Trick! Normalerweise gehe ich ohne Nohup, aber ich werde definitiv Verwendung für dieses finden.
Joel
Toll! Ich weiß nicht, ob es der richtige Weg ist, aber es funktioniert wie ein Zauber.
Marc MAURICE
1
Das scheint nicht zu trennen stdin, stdout, stderr. Zumindest nicht mit sh.
Craig McQueen
3
Diese Methode zum Abnehmen wird häufig verwendet. Es heißt "Doppelgabel" und wird in einer der heiligen UNIX-Schriften, 2nd Stevens ( amazon.com/dp/0201433079 ), ausführlicher erklärt .
Dave
1
Weitere technische Informationen zu Double Fork und Setsid () finden Sie unter thelinuxjedi.blogspot.com/2014/02/… . Lesen Sie besonders den Abschnitt "Die Panne", in dem steht: "Die wirklichen Schritte hinter der Doppelgabel sind wie folgt:"
Mike S
4

Es hängt wirklich davon ab, was die Binärdatei selbst tun wird.

Zum Beispiel möchte ich einen Listener erstellen.

Der Start-Daemon ist eine einfache Aufgabe:

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

So starten wir den Daemon (gemeinsame Methode für alle /etc/init.d/ Mitarbeiter)

Was nun den Hörer selbst betrifft, muss es eine Art Schleife / Alarm sein, sonst wird das Skript ausgelöst, um das zu tun, was Sie wollen. Wenn Sie beispielsweise möchten, dass Ihr Skript 10 Minuten lang schläft und aufwacht und Sie fragt, wie es Ihnen geht, tun Sie dies mit dem

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

Hier ist der einfache Listener, den Sie ausführen können, der auf Ihre Befehle vom Remote-Computer wartet und diese lokal ausführt:

Zuhörer:

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

So starten Sie UP: / tmp / deamon_test / listener start

und um Befehle von der Shell zu senden (oder sie in ein Skript zu verpacken):

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)

Hoffe das wird helfen.

Artem
quelle
2

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

Congbin Guo
quelle
1

Wenn ich ein hatte script.shund es von Bash ausführen und laufen lassen wollte, auch wenn ich meine Bash-Sitzung schließen möchte, würde ich es kombinieren nohupund &am Ende.

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

inputFile.txtkann eine beliebige Datei sein. Wenn Ihre Datei keine Eingabe hat, verwenden wir normalerweise /dev/null. Der Befehl wäre also:

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

Schließen Sie danach Ihre Bash-Sitzung, öffnen Sie ein anderes Terminal und führen ps -aux | egrep "script.sh"Sie Folgendes aus: und Sie werden sehen, dass Ihr Skript noch im Hintergrund ausgeführt wird. Wenn Sie es stoppen möchten, führen Sie natürlich den gleichen Befehl (ps) und auskill -9 <PID-OF-YOUR-SCRIPT>

kein Name
quelle
0

Siehe Bash Service Manager- Projekt: https://github.com/reduardo7/bash-service-manager

Implementierungsbeispiel

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

Anwendungsbeispiel

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

Wie viele Antworten ist diese keine "echte" Dämonisierung, sondern eine Alternative zum nohupAnsatz.

echo "script.sh" | at now

Es gibt offensichtlich Unterschiede zur Verwendung nohup. Zum einen gibt es überhaupt keine Trennung vom Elternteil. Außerdem erbt "script.sh" nicht die Umgebung der Eltern.

Dies ist keineswegs eine bessere Alternative. Es ist einfach eine andere (und etwas faule) Art, Prozesse im Hintergrund zu starten.

PS Ich persönlich habe Carlos Antwort positiv bewertet, da sie die eleganteste zu sein scheint und sowohl über Terminal- als auch über Inside-Skripte funktioniert

oᴉɹǝɥɔ
quelle
-3

Versuchen Sie es mit &, wenn Sie diese Datei als program.sh speichern

Sie können verwenden

$. program.sh &
Jasimmk
quelle