制作shell脚本守护进程的最佳方法是什么?

时间:2010-08-07 12:04:07

标签: bash shell daemon

我想知道是否有更好的方法来制作一个只使用sh等待某事的守护进程:

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

while true; do
  sleep 1000
done

特别是,我想知道是否有任何方法可以摆脱循环,仍然可以监听信号。

12 个答案:

答案 0 :(得分:107)

只需后台编写脚本(./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 /)和fork,以便父进程退出,从而关闭原始描述符。
  • 也许运行umask 0。您可能不想依赖守护程序调用者的umask。

有关考虑所有这些方面的脚本示例,请参阅Mike S' answer

答案 1 :(得分:64)

这里的一些最热门的答案缺少使守护进程成为守护进程的一些重要部分,而不仅仅是后台进程,或者是从shell分离的后台进程。

这个http://www.faqs.org/faqs/unix-faq/programmer/faq/描述了成为守护进程的必要条件。这个Run bash script as daemon实现了setsid,虽然它错过了chdir to root。

原始海报的问题实际上比“我如何使用bash创建守护进程?”更具体,但由于主题和答案一般讨论守护进程的shell脚本,我认为重要的是指出它(对于像我这样的闯入者)查看创建守护进程的详细信息。)

这是我根据常见问题解答执行的shell脚本的再现。将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

答案 2 :(得分:61)

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

答案 3 :(得分:43)

使用系统的守护程序工具,例如start-stop-daemon

否则,是的,某处必须有一个循环。

答案 4 :(得分: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

并从shell发送命令(或将其包装到脚本中):

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)

希望这会有所帮助。

答案 5 :(得分:1)

查看libslack包中的守护进程工具:

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

在Mac OS X上,为shell守护程序使用launchd脚本。

答案 6 :(得分: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",您将看到您的脚本仍在后台运行。对于cource,如果要停止它,则执行相同的命令(ps)和kill -9 <PID-OF-YOUR-SCRIPT>

答案 7 :(得分:0)

请参阅 Bash服务管理器项目: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

答案 8 :(得分:0)

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

答案 9 :(得分:0)

像许多答案一样,这不是“真正的”守护进程,而是http://username:password@example.com方法的替代方法。

nohup

使用echo "script.sh" | at now 显然有区别。对于一个人,首先没有与父母分离。同样,“ script.sh”不会继承父级的环境。

绝不是更好的选择。这只是在后台启动进程的另一种方式(并且有些懒惰)。

P.S。我个人赞成carlo的回答,因为它似乎是最优雅的,并且可以在终端脚本和内部脚本中使用

答案 10 :(得分:0)

这是在Bourne shell(或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行:wait等待等待睡眠结束,但被(某些)信号打断。
  • 第19行:杀死睡眠进程,因为当捕获到信号后,该进程仍在运行。
  • 第22行:如果已捕获SIGUSR1,则进行工作。

猜猜它没有比这更简单的了。

答案 11 :(得分:-3)

尝试使用&amp; 如果您将此文件另存为program.sh

你可以使用

$. program.sh &