Найкращий спосіб зробити демон сценарію оболонки?


82

Мені цікаво, чи є кращий спосіб зробити демон, який чекає чогось, використовуючи лише sh, ніж:

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

while true; do
  sleep 1000
done

Зокрема, мені цікаво, чи є якийсь спосіб позбутися циклу і все одно змусити цю річ слухати сигнали.


3
Вам знадобиться цикл, але зверніть увагу, що ваш приклад, мабуть, не буде виконуватись так, як ви очікували. Сон не є вбудованою оболонкою, і SIGUSR1, отриманий оболонкою, не поширюється на дочірні процеси. Таким чином, ваш обробник сигналу не буде оброблений, поки не закінчиться режим сну. Див. Mywiki.wooledge.org/SignalTrap#preview , третій розділ.
Mike S

Відповіді:



119

Просто фонове зображення вашого сценарію ( ./myscript &) не дасть демонізувати його. Див. Http://www.faqs.org/faqs/unix-faq/programmer/faq/ , розділ 1.7, де описано, що необхідно для того, щоб стати демоном. Ви повинні відключити його від терміналу, щоб SIGHUPне вбити. Ви можете скористатися комбінацією клавіш, щоб скрипт діяв як демон;

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

зробить роботу. Або, щоб захопити як файл stderr, так і stdout у файл:

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

Однак можуть бути й інші важливі аспекти, які вам потрібно врахувати. Наприклад:

  • Ви все ще матимете дескриптор файлу, відкритий для сценарію, а це означає, що каталог, в який він змонтований, буде незмінним. Ви повинні бути справжнім демономchdir("/") (або cd /всередині вашого сценарію) форк так, щоб вийшов батько, і таким чином оригінальний дескриптор закрився.
  • Можливо, біжи umask 0. Можливо, ви не хочете залежати від umask абонента демона.

Для прикладу сценарію , який приймає всі ці аспекти до уваги, см відповідь Mike S » .


1
По-перше, дякую за цю відповідь. В основному це працює дуже добре для мене. АЛЕ я хотів би додати файл журналу, і коли я спробую "& >> log.txt" Я отримую цю помилку ... ", помилка синтаксису біля несподіваного маркера`> '", будь-які ідеї для мене?
Том Страттон,

7
Що робить 0<&-? Не очевидно, що ця послідовність символів виконує.
Крейг МакКвін

13
Не очевидно, що 0<&-потрібно робити. Я знайшов це посилання, яке це пояснює.
Крейг МакКвін

2
Це правильно, 0<&-закриває stdin (fd 0). Таким чином, якщо ваш процес випадково прочитає з stdin (це легко зробити), він отримає помилку, замість того, щоб повіситися назавжди, чекаючи появи даних.
Бронсон

1
nohup автоматично перенаправляє stdin з / dev / null (див. посібник). Закриття дескрипторів файлів std - не найкраща практика.
фітиль

75

У деяких із запропонованих тут відповідей відсутні деякі важливі частини того, що робить демон демоном, на відміну від простого фонового процесу або фонового процесу, від'єднаного від оболонки.

Цей http://www.faqs.org/faqs/unix-faq/programmer/faq/ описує, що необхідно, щоб бути демоном. І цей скрипт запуску bash як демон реалізує setid, хоча він втрачає кореневий chdir.

Питання оригінального плаката насправді було більш конкретним, ніж "Як створити процес демона за допомогою bash?", Але оскільки тема та відповіді обговорюють демонізацію сценаріїв оболонки загалом, я вважаю важливим вказати на це (для таких осіб, як я, які вивчають дрібні деталі створення демона).

Ось моє виконання сценарію оболонки, який би поводився відповідно до FAQ. Встановіть DEBUG, щоб trueбачити гарний результат (але він також виходить негайно, а не циклічно):

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

Результат виглядає так, коли DEBUGвстановлено значення true. Зверніть увагу на те, як змінюються номери сеансу та ідентифікатора групи процесів (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) будь-яка інтернаціональна присутність, крім SO (блог, соціальна мережа, взагалі?). Я не бачив нічого подібного в інформації вашого профілю.
Michaël Le Barbier

@ MichaelGrünewald Формально не надто. Боюсь, що я не такий цікавий; Я багато не веду блог. Я володію доменом schwager.com (на даний момент у нього мало), і у мене є кілька проектів Arduino. Моїм номіналом є GreyGnome, загалом. Ви можете знайти Google GreyGnome або GreyGnome + Arduino і знайти мене поруч.
Mike S

1
@MikeS Дякую! Я запитую, бо не так часто можна зустріти людей, які справді цікавляться програмуванням оболонки, і я завжди із задоволенням обмінююся ідеями та думками з цього приводу. Дякуємо за додаткову інформацію! :)
Michaël Le Barbier

@MikeS, навіщо тобі два рази форкувати? У вашому коді онук батьківського сценарію стає новим лідером сеансу з setsid, так? Чому ви не можете просто зробити це для першої вилки?
promaty

З поширених запитань, на які я посилався у своєму дописі: "Ось кроки, щоб стати демоном: ... 1. fork()' so the parent can exit, this returns control to the command line or shell invoking your program. ... 2. setsid () ', щоб стати лідером групи процесів та групи сеансів ... наш процес зараз не має контрольного терміналу, який є Хороша річ для демонів ... 3. Знову `fork () ', щоб батько ... міг вийти. Це означає, що ми, як керівник несесійної групи, ніколи не можемо повернути контрольний термінал."
Mike S

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

Класний фокус! Зазвичай я їду без будь-чого, але для цього я точно знайду користь.
Джоел

Чудово! Не знаю, чи це правильно, але це працює як шарм.
Марк МОРІС

1
Це , здається, не відключитися stdin, stdout, stderr. Принаймні не з sh.
Крейг МакКвін

3
Цей спосіб від'єднання застосовується зовсім небагато. Це називається "подвійна вилка" і пояснюється більш детально в одному зі святих Писань UNIX, 2-й Стівенс ( amazon.com/dp/0201433079 ).
Dave

1
Для отримання додаткової технічної інформації про подвійну вилку та setsid (), див. Thelinuxjedi.blogspot.com/2014/02/… . Особливо прочитайте розділ "Розбивка", де сказано: "Реальні кроки за подвійною вилкою такі:"
Mike S

4

Це насправді залежить від того, що буде робити сам бінарний файл.

Наприклад, я хочу створити слухача.

Початковий демон - це просте завдання:

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

ось як ми запускаємо демон (загальний спосіб для всіх /etc/init.d/ співробітників)

тепер, що стосується самого слухача, то це має бути якась петля / попередження, інакше це спричинить скрипт робити те, що ти хочеш. Наприклад, якщо ви хочете, щоб ваш сценарій спав 10 хв, прокинувся і запитав вас, як у вас справи, ви зробите це за допомогою

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

Ось простий прослуховувач, який ви можете зробити, який буде слухати ваші команди з віддаленої машини і виконувати їх на локальному:

слухач:

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

Тож для запуску: / tmp / deamon_test / listener start

і надсилати команди з оболонки (або перенести в сценарій):

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)

Сподіваюся, це допоможе.




1

Якби у мене був script.shі я хотів виконати його з bash і залишити його запущеним, навіть коли я хочу закрити свій сеанс bash, тоді я б комбінував nohupі &в кінці.

приклад: nohup ./script.sh < inputFile.txt > ./logFile 2>&1 &

inputFile.txtможе бути будь-яким файлом. Якщо у вашому файлі немає вводу, ми зазвичай використовуємо /dev/null. Отже, команда буде такою:

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

Після цього закрийте сеанс bash, відкрийте інший термінал і запустіть: ps -aux | egrep "script.sh"і ви побачите, що ваш сценарій все ще працює у фоновому режимі. Звичайно, якщо ви хочете його зупинити, виконайте ту ж команду (ps) таkill -9 <PID-OF-YOUR-SCRIPT>


0

Див. Проект Bash Service Manager : https://github.com/reduardo7/bash-service-manager

Приклад реалізації

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

Приклад використання

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

Як і на багато відповідей, і цей не є "справжньою" демонізацією, а скоріше альтернативою nohupпідходу.

echo "script.sh" | at now

Очевидно, є відмінності від використання nohup. По-перше, це не відрив від батьків. Також "script.sh" не успадковує батьківське середовище.

Це далеко не краща альтернатива. Це просто інший (і дещо лінивий) спосіб запуску процесів у фоновому режимі.

PS Я особисто підтримав відповідь Карло, оскільки вона видається найелегантнішою і працює як з термінальних, так і з внутрішніх скриптів


0

Ось мінімальна зміна початкової пропозиції щодо створення дійсного демона в оболонці Bourne (або 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

Пояснення:

  • Рядок 2-7: Демон повинен бути роздвоєним, щоб у нього не було батьківського елемента. Використання штучного аргументу для запобігання нескінченному роздвоєнню. "setsid" від'єднується від запуску процесу та терміналу.
  • Рядок 9: Наш бажаний сигнал потрібно диференціювати від інших сигналів.
  • Рядок 10: Очищення потрібно, щоб позбутися звисаючих процесів "сну".
  • Рядок 11-13: Переспрямуйте stdout, stderr та stdin сценарію.
  • Рядок 16: сон у фоновому режимі
  • Рядок 18: чекайте, поки чекає кінець сну, але його перебивають (деякі) сигнали.
  • Рядок 19: Вимкніть процес сну, оскільки він все ще працює, коли сигнал потрапляє.
  • Рядок 22: Виконайте роботу, якщо SIGUSR1 був спійманий.

Думаю, це не стає простішим.


-3

спробуйте виконати за допомогою & якщо ви збережете цей файл як program.sh

ви можете використовувати

$. program.sh &
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.