Лучший способ сделать демон сценария оболочки? - PullRequest
71 голосов
/ 07 августа 2010

Мне интересно, есть ли лучший способ создать демона, который ждет чего-то, используя только sh, чем:

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

while true; do
  sleep 1000
done

В частности, мне интересно, есть ли какой-нибудь способ избавиться от петли и все же заставить вещь слушать сигналы.

Ответы [ 11 ]

107 голосов
/ 06 июня 2012

Фоновая обработка сценария (./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. Возможно, вы не захотите зависеть от маскировки вызывающей стороны демона.

Пример сценария, который учитывает все эти аспекты, см. В Mike S 'answer .

64 голосов
/ 17 марта 2015

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

Это http://www.faqs.org/faqs/unix-faq/programmer/faq/ описывает, что необходимо, чтобы быть демоном.И этот Запуск скрипта bash от имени демона реализует 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 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.

Вывод выглядит так, когда DEBUG установлен на true.Обратите внимание, как меняются номера ID сеанса и группы процессов (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
61 голосов
/ 07 августа 2010
# double background your script to have it detach from the tty
# cf. http://www.linux-mag.com/id/5981 
(./program.sh &) & 
44 голосов
/ 07 августа 2010

Используйте средство демона вашей системы, например start-stop-daemon .

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

4 голосов
/ 28 января 2014

Это действительно зависит от того, что будет делать сам двоичный файл.

Например, я хочу создать слушателя.

Запуск демона - простая задача:

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/ staff)

теперь что касается самого слушателя, Это должен быть какой-то цикл / предупреждение, иначе скрипт будет запускаться делать то, что ты хочешь. Например, если вы хотите, чтобы ваш скрипт спал 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 / start listener

и отправлять команды из оболочки (или переносить их в скрипт):

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 голос
/ 01 октября 2015

Если бы у меня был 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>

1 голос
/ 07 августа 2010

Посмотрите на инструмент демона из пакета libslack:

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

В Mac OS X используйте скрипт launchd для демона оболочки.

0 голосов
/ 19 сентября 2018

Как и многие ответы, этот ответ не является «реальной» демонизацией, а скорее альтернативой nohup подходу.

echo "script.sh" | at now

Очевидно, что использование nohup очевидно.Для одного нет отрыва от родителя в первую очередь.Также «script.sh» не наследует родительскую среду.

Ни в коем случае это не лучшая альтернатива.Это просто другой (и несколько ленивый) способ запуска процессов в фоновом режиме.

PS Я лично одобрил ответ Карло, так как он кажется самым элегантным и работает как из терминальных, так и внутри скриптов

0 голосов
/ 09 марта 2018

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

0 голосов
/ 03 июня 2017

См. 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
...