制作Shell脚本守护程序的最佳方法?


81

我想知道是否有比使用sh更好的方法来制作仅使用sh进行等待的守护程序:

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

while true; do
  sleep 1000
done

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


3
您将需要一个循环,但是请注意,您的示例可能不会以您期望的方式执行。sleep不是内置的shell,并且shell接收到的SIGUSR1不会传播到子进程。因此,直到睡眠完成,您的信号处理程序才会得到处理。请参阅mywiki.wooledge.org/SignalTrap#preview(第3部分)。
Mike S

Answers:



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”时,出现此错误...“意外令牌'>'附近的语法错误”对我有什么想法?
汤姆·斯特拉顿2013年

7
怎么0<&-办?字符序列完成的功能尚不清楚。
Craig McQueen

13
目前尚不清楚0<&-该做什么。我找到了解释它的链接
Craig McQueen

2
没错,请0<&-关闭stdin(fd 0)。这样,如果您的进程意外地从stdin读取(易于执行),它将得到一个错误,而不是永远挂起等待数据显示。
bronson

1
nohup自动从/ ​​dev / null重定向标准输入(请参阅手册)。关闭标准文件描述符不是最佳实践。
威克

74

这里的一些最重要的答案都缺少使守护程序成为守护程序的某些重要部分,这与后台进程或从shell分离的后台进程相反。

http://www.faqs.org/faqs/unix-faq/programmer/faq/描述了成为守护程序的必要条件。而且,该运行bash脚本作为守护程序实现了setid,尽管它会丢失chdir到根目录。

原始发布者的问题实际上比“我如何使用bash创建守护进程?”更具体,但是由于主题和答案都讨论了一般情况下如何守护shell脚本,因此我指出这一点很重要(对于像我这样的闯入者,创建守护程序的详细信息)。

这是我根据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。请注意会话和进程组标识(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

这是一个很好的答案!除了SO(博客,社交网络等)之外,您(Mike S)是否还有其他在线存在方式?我在您的个人资料信息中没有看到类似的信息。
迈克尔乐巴比尔

@MichaelGrünewald不太正式。恐怕我没那么有趣。我的博客不多。我拥有schwager.com域(目前该域很少),并且确实有一些Arduino项目。通常,我的称呼是GreyGnome。您可以使用Google GreyGnome或GreyGnome + Arduino来找我。
Mike S

1
@MikeS谢谢!我之所以问是因为,遇到真正对shell编程感兴趣的人并不常见,我总是很乐意就此交换意见和看法。感谢您提供的补充信息!:)
迈克尔乐巴比尔

@MikeS,为什么需要叉两次?在您的父脚本的代码孙代中,成为具有setid的新会话领导者,是吗?您为什么不能只为第一个叉子这样做?
promaty '16

从我在帖子中引用的常见问题解答中:“以下是成为守护程序的步骤:... 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,但是我肯定会发现它有用。
joel 2012年

大!我不知道这是否是正确的方法,但是它就像一种魅力。
马克·莫里斯

1
这似乎并没有断开连接stdinstdoutstderr。至少没有sh
Craig McQueen

3
这种分离方法被大量使用。它被称为“双叉”,在UNIX圣经中的第二个史蒂文斯(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

这是您可以执行的简单侦听器,它将侦听来自远程计算机的命令并在本地执行它们:

听众:

#!/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)

希望这会有所帮助。




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"您将看到您的脚本仍在后台运行。当然,如果要停止它,请执行相同的命令(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我个人赞成carlo的回答,因为这似乎是最优雅的,并且可以在终端脚本和内部脚本中使用


-3

如果将此文件另存为program.sh,请尝试使用&执行

您可以使用

$. program.sh &
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.