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

Sha*_*off 76 bash shell daemon

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

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

while true; do
  sleep 1000
done
Run Code Online (Sandbox Code Playgroud)

特别是,我想知道是否有任何方法摆脱循环,仍然有东西听取信号.

小智 109

只是后台你的脚本(./myscript &)不会守护它.请参阅http://www.faqs.org/faqs/unix-faq/programmer/faq/,第1.7节,其中描述了成为守护进程所需的内容.您必须断开它与终端的连接,以免它终止SIGHUP它.您可以使用快捷方式使脚本看起来像守护进程;

nohup ./myscript 0<&- &>/dev/null &
Run Code Online (Sandbox Code Playgroud)

会做的.或者,将stderr和stdout捕获到文件中:

nohup ./myscript 0<&- &> my.admin.log.file &
Run Code Online (Sandbox Code Playgroud)

但是,您可能需要考虑其他重要方面.例如:

  • 您仍然可以在脚本中打开一个文件描述符,这意味着它所挂载的目录将是无法安装的.要成为一个真正的守护进程,你应该chdir("/")(或cd /在你的脚本中)和fork,以便父进程退出,从而关闭原始描述符.
  • 也许跑umask 0.您可能不想依赖守护程序调用者的umask.

有关将所有这些方面考虑在内的脚本示例,请参阅Mike S的回答.

  • 什么是"0 <& - "意味着什么并不明显.我发现[此链接](http://wiki.bash-hackers.org/howto/redirection_tutorial)解释了它. (13认同)
  • `0 <& - `做什么?这个字符序列完成的内容并不明显. (7认同)
  • 首先,感谢您的回答。它对我来说大部分工作得很好。但是我想附加到日志文件,当我尝试“&amp;&gt;&gt; log.txt”时,我收到此错误...“意外标记`&gt;'附近的语法错误”对我有什么想法吗? (2认同)
  • 这是正确的,`0 <& - `关闭stdin(fd 0).这样,如果你的进程意外地从stdin读取(容易做),它将得到一个错误,而不是永远等待数据出现. (2认同)
  • nohup 自动从 /dev/null 重定向 stdin(请参阅手册)。关闭 std 文件描述符不是最佳实践。 (2认同)

Mik*_*e S 66

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

这篇http://www.faqs.org/faqs/unix-faq/programmer/faq/描述了成为守护进程的必要条件.这个运行bash脚本作为守护进程实现了setsid,虽然它错过了chdir到root.

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

这是我根据FAQ运行的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.
Run Code Online (Sandbox Code Playgroud)

输出看起来是这样的,当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
Run Code Online (Sandbox Code Playgroud)


小智 61

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

  • 这种分离方法使用了很多.它被称为"双叉",并在一个神圣的UNIX经文,第二史蒂文斯(http://www.amazon.com/dp/0201433079)中进行了更深入的解释. (3认同)
  • 有关双叉和setsid()的更多技术信息,请参阅http://thelinuxjedi.blogspot.com/2014/02/why-use-double-fork-to-daemonize.html。特别要阅读“故障分析”部分,其中提到“双分叉背后的真正步骤如下:” (3认同)

Pau*_*ce. 45

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

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

  • start-stop-daemon使用起来非常方便 (2认同)

Con*_*Guo 5

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