쉘 스크립트 데몬을 만드는 가장 좋은 방법은 무엇입니까?


81

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

답변:



118

스크립트 ( ./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 2013 년

7
무엇을 0<&-합니까? 일련의 문자가 무엇을 성취하는지는 분명하지 않습니다.
Craig McQueen 2013 년

13
무엇을해야하는지 0<&-는 분명 하지 않습니다. 나는 그것을 설명하는 이 링크 를 찾았 습니다.
Craig McQueen

2
맞습니다. 0<&-stdin (fd 0)을 닫습니다. 이렇게하면 프로세스가 실수로 stdin에서 읽은 경우 (쉽게 수행 할 수 있음) 데이터가 표시 될 때까지 계속 기다리지 않고 오류가 발생합니다.
bronson 2015-06-05

1
nohup은 stdin을 / dev / null에서 자동으로 리디렉션합니다 (매뉴얼 참조). std 파일 설명자를 닫는 것은 모범 사례가 아닙니다.
심지

74

여기에서 가장 많이 뽑힌 답변 중 일부는 백그라운드 프로세스 또는 셸에서 분리 된 백그라운드 프로세스와 달리 데몬을 데몬으로 만드는 중요한 부분이 누락되었습니다.

http://www.faqs.org/faqs/unix-faq/programmer/faq/는 데몬으로 필요한 것을 설명합니다. 그리고이 Run bash script as daemon 은 setsid를 구현하지만 루트에 대한 chdir이 누락되었습니다.

원래 포스터의 질문은 실제로 "Bash를 사용하여 데몬 프로세스를 만드는 방법"보다 더 구체적 이었지만 주제와 답변은 일반적으로 쉘 스크립트 데몬 화에 대해 논의하기 때문에 지적하는 것이 중요하다고 생각합니다. 데몬 생성에 대한 세부 정보).

다음은 FAQ에 따라 작동하는 쉘 스크립트의 제 표현입니다. true예쁜 출력을 보려면 DEBUG를 로 설정 하십시오 (그러나 끝없이 반복되는 대신 즉시 종료됩니다).

#!/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. 세션 및 프로세스 그룹 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

아주 좋은 대답입니다! 귀하 (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

내 게시물에서 참조한 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을 사용하지 않지만 확실히 이것을 사용할 것입니다.
조엘

큰! 옳은 길인지는 모르겠지만 매력처럼 작동한다.
Marc MAURICE

1
즉, 분리하지 않는 것 stdin, stdout, stderr. 적어도 sh.
Craig McQueen

3
이 분리 방법은 꽤 많이 사용됩니다. "이중 포크"라고하며 거룩한 UNIX 경전 중 하나 인 2nd Stevens ( 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/ 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 / 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

a가 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"는 부모의 환경을 상속하지 않습니다.

결코 이것이 더 나은 대안이 아닙니다. 단순히 백그라운드에서 프로세스를 시작하는 다른 (그리고 다소 게으른) 방법입니다.

추신 : 나는 개인적으로 carlo의 대답을 찬성했습니다. 가장 우아하고 터미널과 내부 스크립트 모두에서 작동합니다.


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이 잡힌 경우 작업을 수행합니다.

그것보다 더 간단하지 않다고 생각하십시오.


-2

이 파일을 program.sh로 저장하면 &를 사용하여 실행 해보십시오.

당신이 사용할 수있는

$. program.sh &
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.