Лучший способ создать демон сценария оболочки?


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" я получаю эту ошибку ... "синтаксическая ошибка рядом с неожиданным токеном`> '"какие-нибудь идеи для меня?
Tom Stratton

7
Что 0<&-делать? Непонятно, что делает эта последовательность символов.
Craig McQueen

13
Непонятно, что 0<&-нужно делать. Я нашел эту ссылку, которая объясняет это.
Craig McQueen

2
Правильно, 0<&-закрывает stdin (fd 0). Таким образом, если ваш процесс случайно читает из стандартного ввода (легко сделать), он получит сообщение об ошибке вместо того, чтобы вечно зависать в ожидании появления данных.
bronson 05

1
nohup автоматически перенаправляет stdin из / dev / null (см. руководство). Закрытие дескрипторов файлов std - не лучшая практика.
wick

75

В некоторых ответах, получивших наибольшее количество голосов, здесь отсутствуют некоторые важные части того, что делает демона демоном, в отличие от просто фонового процесса или фонового процесса, отделенного от оболочки.

Этот http://www.faqs.org/faqs/unix-faq/programmer/faq/ описывает, что необходимо для работы демона. И этот сценарий Run bash as daemon реализует setsid, хотя он пропускает chdir для root.

Вопрос оригинального плаката был на самом деле более конкретным, чем «Как мне создать процесс-демон с помощью 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 (блог, социальная сеть, что угодно?) Я не видел ничего подобного в информации вашего профиля.
Микаэль Ле Барбье

@ MichaelGrünewald Не так уж и много формально. Боюсь, я не такой уж интересный; Я не веду много блогов. У меня есть домен schwager.com (которого на данный момент мало), и у меня есть несколько проектов Arduino. Обычно мой псевдоним - GreyGnome. Вы можете Google GreyGnome или GreyGnome + Arduino и найти меня поблизости.
Mike S

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

@MikeS, а зачем тебе два раза форк? В вашем коде внук родительского скрипта становится новым лидером сеанса с помощью setsid, да? Почему вы не можете сделать это для первой вилки?
promaty

Из FAQ, на который я ссылался в своем сообщении: «Вот шаги, чтобы стать демоном: ... 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 &) & 

Классный трюк! Обычно я иду без nohup, но я обязательно найду ему применение.
Джоэл

Большой! Не знаю, правильный ли это способ, но работает как шарм.
Марк МОРИС

1
Это , кажется, не отключиться stdin, stdout, stderr. По крайней мере, не с sh.
Крейг МакКуин,

3
Этот способ отсоединения используется довольно часто. Это называется «двойной вилкой» и более подробно объясняется в одном из священных писаний UNIX, 2nd Stevens ( amazon.com/dp/0201433079 ).
Дэйв

1
Для получения дополнительной технической информации о двойной вилке и setsid () см. Thelinuxjedi.blogspot.com/2014/02/… . Особенно прочтите раздел «Разборка», где говорится: «Настоящие шаги за двойной вилкой следующие:»
Майк С.

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.