Najlepszy sposób na stworzenie demona skryptu powłoki?

82

Zastanawiam się, czy jest lepszy sposób na stworzenie demona, który czeka na coś używając tylko sh niż:

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

while true; do
  sleep 1000
done

W szczególności zastanawiam się, czy istnieje sposób, aby pozbyć się pętli i nadal mieć urządzenie nasłuchujące sygnałów.

Shawn J. Goff
źródło
3
Będziesz potrzebować pętli, ale pamiętaj, że Twój przykład prawdopodobnie nie będzie działał w oczekiwany sposób. Uśpienie nie jest wbudowane w powłokę, a otrzymany przez powłokę SIGUSR1 nie jest propagowany do procesów potomnych. W ten sposób program obsługi sygnału nie zostanie przetworzony, dopóki sen nie zostanie zakończony. Zobacz mywiki.wooledge.org/SignalTrap#preview , trzecia sekcja.
Mike S

Odpowiedzi:

49

Użyj funkcji demona systemu, takiej jak start-stop-daemon .

W przeciwnym razie tak, gdzieś musi być pętla.

Dennis Williamson
źródło
2
start-stop-daemon był zaskakująco wygodny w użyciu
mpartel
URL jest już nieaktualny.
erenon
119

Samo umieszczenie skryptu w tle ( ./myscript &) nie daemonizuje go. Zobacz http://www.faqs.org/faqs/unix-faq/programmer/faq/ , sekcja 1.7, w której opisano, co jest konieczne, aby zostać demonem. Musisz go odłączyć od terminala, żeby SIGHUPgo nie zabić. Możesz użyć skrótu, aby skrypt wyglądał jak demon;

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

wykona robotę. Lub, aby przechwycić stderr i stdout do pliku:

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

Jednak mogą istnieć inne ważne aspekty, które należy wziąć pod uwagę. Na przykład:

  • Nadal będziesz mieć otwarty deskryptor pliku dla skryptu, co oznacza, że ​​katalog, w którym jest on zamontowany, byłby nie do zamontowania. Aby być prawdziwym demonem, powinieneś chdir("/")(lub cd /wewnątrz swojego skryptu) i rozwidlić, aby rodzic zakończył działanie, a tym samym oryginalny deskryptor został zamknięty.
  • Może uciekaj umask 0. Możesz nie chcieć polegać na umasce wywołującego demona.

Na przykład skrypt, który ma wszystkie te aspekty pod uwagę, patrz odpowiedź Mike S” .

Ken B
źródło
1
Po pierwsze, dzięki za tę odpowiedź. U mnie to działa bardzo dobrze. ALE chciałbym dołączyć do pliku dziennika, a kiedy próbuję „& >> log.txt” pojawia się ten błąd ... ”, błąd składni w pobliżu nieoczekiwanego tokenu„> ”” jakieś pomysły dla mnie?
tom stratton
7
Co robi 0<&-? Nie jest oczywiste, co osiąga ta sekwencja znaków.
Craig McQueen
13
Nie jest oczywiste, co 0<&-ma robić. Znalazłem ten link, który to wyjaśnia.
Craig McQueen
2
Zgadza się, 0<&-zamyka stdin (fd 0). W ten sposób, jeśli twój proces przypadkowo odczyta ze standardowego wejścia (łatwe do zrobienia), otrzyma błąd zamiast zawieszać się wiecznie, czekając na pojawienie się danych.
Bronson
1
nohup automatycznie przekierowuje stdin z / dev / null (zobacz instrukcję). Zamykanie deskryptorów plików std nie jest najlepszym rozwiązaniem.
knot
75

W niektórych odpowiedziach, które zostały tutaj zarzucone, brakuje niektórych ważnych części tego, co sprawia, że ​​demon jest demonem, w przeciwieństwie do samego procesu w tle lub procesu w tle odłączonego od powłoki.

Ten http://www.faqs.org/faqs/unix-faq/programmer/faq/ opisuje, co jest niezbędne, aby być demonem. I ten skrypt Run bash jako demon implementuje id seta, chociaż pomija chdir do roota.

Pytanie pierwotnego autora było w rzeczywistości bardziej szczegółowe niż „Jak utworzyć proces demona przy użyciu basha?”, Ale ponieważ temat i odpowiedzi omawiają generalnie skrypty powłoki demonizujące, myślę, że ważne jest, aby je wskazać (dla intruzów takich jak ja drobne szczegóły tworzenia demona).

Oto moja wersja skryptu powłoki, który zachowywałby się zgodnie z FAQ. Ustaw DEBUG, aby truezobaczył ładny wynik (ale również kończy się natychmiast, zamiast zapętlać się w nieskończoność):

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

Wynik wygląda tak, gdy DEBUGjest ustawiony na true. Zwróć uwagę, jak zmieniają się numery sesji i grup procesów (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.
źródło
To bardzo miła odpowiedź! Czy Ty (Mike S) masz jakąkolwiek obecność w Internecie poza SO (blog, sieć społecznościowa, w ogóle?) Nie widziałem czegoś takiego w informacjach o Twoim profilu.
Michaël Le Barbier
@ MichaelGrünewald Niewiele formalnie. Obawiam się, że nie jestem aż tak interesujący; Nie bloguję dużo. Jestem właścicielem domeny schwager.com (która w tej chwili ma niewiele) i mam kilka projektów Arduino. Mój pseudonim to ogólnie GreyGnome. Możesz Google GreyGnome lub GreyGnome + Arduino i znaleźć mnie w pobliżu.
Mike S
1
@MikeS Thanks! Pytam, ponieważ rzadko spotyka się ludzi szczerze zainteresowanych programowaniem powłoki i zawsze chętnie wymieniam się pomysłami i poglądami na ten temat. Dziękuję za te dodatkowe informacje! :)
Michaël Le Barbier
@MikeS, dlaczego musisz dwa razy rozwidlać? W twoim kodzie wnuk rodzica zostaje liderem nowej sesji z ustawieniami, tak? Dlaczego nie możesz tego zrobić dla pierwszego widelca?
promaty
Z FAQ, do którego odwoływałem się w moim poście: "Oto kroki, aby zostać demonem: ... 1. fork()' so the parent can exit, this returns control to the command line or shell invoking your program. ... 2. setsid ()", aby zostać grupą procesów i liderem grupy sesji ... nasz proces nie ma teraz terminala sterującego, którym jest Dobra Rzecz dla demonów… 3. „fork ()” ponownie, aby rodzic… mógł wyjść. Oznacza to, że my, jako lider grupy bez sesji, nigdy nie odzyskamy kontrolującego terminala. "
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
źródło
Fajna sztuczka! Zwykle chodzę bez żadnych problemów, ale na pewno znajdę zastosowanie dla tego.
joel
Świetny! Nie wiem, czy to właściwy sposób, ale działa jak urok.
Marc MAURICE
1
Nie wydaje się, aby odłączyć stdin, stdout, stderr. Przynajmniej nie z sh.
Craig McQueen
3
Ta metoda odłączania jest używana dość często. Nazywa się to „podwójnym widelcem” i jest dokładniej wyjaśnione w jednym ze świętych pism systemu UNIX, 2nd Stevens ( amazon.com/dp/0201433079 ).
Dave
1
Aby uzyskać więcej informacji technicznych na temat podwójnego widelca i setid (), zobacz thelinuxjedi.blogspot.com/2014/02/… . Szczególnie przeczytaj sekcję „Podział”, w której jest napisane: „Prawdziwe kroki za podwójnym widelcem są następujące:”
Mike S,
4

To naprawdę zależy od tego, co zrobi sam plik binarny.

Na przykład chcę stworzyć słuchacza.

Uruchomienie demona to proste zadanie:

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

w ten sposób uruchamiamy demona (wspólny sposób dla całego personelu /etc/init.d/)

teraz, jeśli chodzi o samego słuchacza, to musi to być jakiś rodzaj pętli / ostrzeżenia, bo inaczej skrypt będzie robił to, co chcesz. Na przykład, jeśli chcesz, aby twój skrypt spał 10 minut i obudził się i zapytał, jak się masz, zrobisz to za pomocą

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

Oto prosty słuchacz, który możesz zrobić, który będzie nasłuchiwał poleceń ze zdalnej maszyny i wykonywał je lokalnie:

słuchacz:

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

Aby go uruchomić: / tmp / deamon_test / listener start

i do wysyłania poleceń z powłoki (lub zawijania do skryptu):

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)

Mam nadzieję, że to pomoże.

Artem
źródło
2

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

Congbin Guo
źródło
1

Gdybym miał script.shi chciałbym wykonać to z basha i zostawić go uruchomionego nawet wtedy, gdy chcę zamknąć moją sesję basha, połączyłbym go nohupi &na końcu.

przykład: nohup ./script.sh < inputFile.txt > ./logFile 2>&1 &

inputFile.txtmoże być dowolnym plikiem. Jeśli twój plik nie ma danych wejściowych, zwykle używamy /dev/null. Więc polecenie byłoby następujące:

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

Następnie zamknij sesję basha, otwórz inny terminal i wykonaj: ps -aux | egrep "script.sh"a zobaczysz, że twój skrypt nadal działa w tle. Oczywiście, jeśli chcesz go zatrzymać, wykonaj to samo polecenie (ps) ikill -9 <PID-OF-YOUR-SCRIPT>

bez nazwy
źródło
0

Zobacz projekt Bash Service Manager : https://github.com/reduardo7/bash-service-manager

Przykład realizacji

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

Przykład użycia

$ ./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
źródło
0

Jak wiele odpowiedzi, ta nie jest „prawdziwą” demonizacją, ale raczej alternatywą dla nohuppodejścia.

echo "script.sh" | at now

Istnieją oczywiście różnice w używaniu nohup. Po pierwsze, nie ma oderwania się od rodzica. Również "script.sh" nie dziedziczy środowiska rodzica.

W żadnym wypadku nie jest to lepsza alternatywa. To po prostu inny (i nieco leniwy) sposób uruchamiania procesów w tle.

PS Osobiście głosowałem za odpowiedzią Carlo, ponieważ wydaje się najbardziej elegancka i działa zarówno z poziomu terminala, jak i wewnątrz skryptów

wyłączone
źródło
0

Oto minimalna zmiana w pierwotnej propozycji stworzenia prawidłowego demona w powłoce Bourne'a (lub 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

Wyjaśnienie:

  • Linia 2-7: Demon musi być rozwidlony, aby nie miał rodzica. Używanie sztucznego argumentu, aby zapobiec niekończącemu się rozwidleniu. „setsid” odłącza się od uruchamiania procesu i terminala.
  • Wiersz 9: Nasz pożądany sygnał należy odróżnić od innych sygnałów.
  • Wiersz 10: Czyszczenie jest wymagane, aby pozbyć się wiszących procesów „uśpienia”.
  • Wiersz 11-13: Przekieruj standardowe wyjście, stderr i stdin skryptu.
  • Wiersz 16: śpij w tle
  • Linia 18: czekaj, czeka na koniec snu, ale przerywają mu (niektóre) sygnały.
  • Wiersz 19: Zabij proces uśpienia, ponieważ nadal działa, gdy sygnał zostanie złapany.
  • Wiersz 22: Wykonaj pracę, jeśli SIGUSR1 został złapany.

Chyba nie ma nic prostszego.

Achim
źródło
-3

spróbuj wykonać za pomocą &, jeśli zapiszesz ten plik jako program.sh

możesz użyć

$. program.sh &
jasimmk
źródło