シェルスクリプトデーモンを作成する最良の方法は?


81

shだけを使用して何かを待機するデーモンを作成するより良い方法があるかどうか疑問に思っています:

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

while true; do
  sleep 1000
done

特に、ループを取り除き、それでも信号をリッスンさせる方法があるかどうか疑問に思っています。


3
ループが必要になりますが、例はおそらく期待どおりに実行されないことに注意してください。スリープはシェルに組み込まれておらず、シェルが受け取ったSIGUSR1は子プロセスに伝播されません。したがって、シグナルハンドラは、スリープが終了するまで処理されません。mywiki.wooledge.org/SignalTrap#previewの3番目のセクションを参照してください。
マイク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に依存したくない場合があります。

これらすべての側面を考慮に入れたスクリプトの例については、MikeSの回答を参照してください。


1
まず、この回答に感謝します。それはほとんど私にとって非常にうまく機能しています。しかし、ログファイルに追加したいのですが、「&>> log.txt」を実行しようとすると、このエラーが発生します...「予期しないトークン `> 'の近くの構文エラー」何かアイデアはありますか?
トムストラトン2013年

7
何をし0<&-ますか?その文字のシーケンスが何を達成するかは明らかではありません。
Craig McQueen

13
0<&-をするのかは明らかではありません。私はそれを説明するこのリンクを見つけました。
Craig McQueen

2
正解です0<&-。stdin(fd 0)を閉じます。そうすれば、プロセスが誤ってstdinから読み取った場合(簡単に実行できます)、データが表示されるのを永遠に待つのではなく、エラーが発生します。
ブロンソン2015年

1
nohupはstdinを/ dev / nullから自動的にリダイレクトします(マニュアルを参照)。stdファイル記述子を閉じることはベストプラクティスではありません。
ウィック2016年

74

ここで最も賛成された回答のいくつかは、バックグラウンドプロセス、またはシェルから切り離されたバックグラウンドプロセスとは対照的に、デーモンをデーモンにする重要な部分が欠落しています。

このhttp://www.faqs.org/faqs/unix-faq/programmer/faq/は、デーモンになるために必要なものについて説明しています。このRun bashスクリプトをデーモンとして実行すると、setsidが実装されますが、ルートへの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 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

とてもいい答えです!あなた(マイクS)はSO(ブログ、ソーシャルネットワークなど)以外にオンラインでの存在感はありますか?あなたのプロフィール情報にはこのようなものは見当たりませんでした。
ミカエル・ル・バルビエ

@MichaelGrünewald正式にはあまりありません。私はそれほど面白くないのではないかと思います。私はあまりブログを書きません。私はschwager.comドメイン(現時点ではほとんどありません)を所有しており、Arduinoプロジェクトをいくつか持っています。私のペンネームは、一般的に、GreyGnomeです。GoogleGreyGnomeまたはGreyGnome + Arduinoで、私を見つけることができます。
マイクS

1
@MikeSありがとう!シェルプログラミングに真剣に興味を持っている人に会うことはあまり一般的ではないので、私は尋ねています、そして私はこれについての考えや意見を交換することをいつも嬉しく思います。その補足情報をありがとう!:)
ミカエル・ル・バルビエ

@MikeS、なぜ2回フォークする必要があるのですか?コードでは、親スクリプトの孫がsetsidを持つ新しいセッションリーダーになりますね。なぜ最初のフォークでこれを実行できないのですか?
promaty 2016

投稿で参照したFAQから:「デーモンになるための手順は次のとおりです。... fork()' so the parent can exit, this returns control to the command line or shell invoking your program. ... 2. 1.setsid()」がプロセスグループおよびセッショングループリーダーになります...プロセスに制御端末がなくなりました。デーモンにとって良いことです... 3。親が...終了できるように再び `fork() '。これは、非セッショングループリーダーとして、制御端末を取り戻すことができないことを意味します。"
マイクS

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

クールなトリック!私は通常nohupなしで行きますが、私は間違いなくこれの用途を見つけるでしょう。
joel 2012年

すごい!それが正しい方法かどうかはわかりませんが、それは魅力のように機能します。
マークモーリス2012年

1
それは切断していないようですstdinstdoutstderr。少なくともsh
Craig McQueen

3
このデタッチ方法はかなり使われています。これは「ダブルフォーク」と呼ばれ、UNIXの聖典の1つである2nd Stevens(amazon.com/dp/0201433079)で詳しく説明されています。
デイブ

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

これは、リモートマシンからのコマンドをリッスンし、ローカルで実行する、uが実行できる簡単なリスナーです。

リスナー:

#!/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 / demon_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.shbashから実行して、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私は個人的にカルロの答えを賛成しました。それは最もエレガントで、ターミナルと内部スクリプトの両方から機能するようです。


-3

このファイルをprogram.shとして保存する場合は、&を使用して実行してみてください

あなたが使用することができます

$. program.sh &
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.