我如何守护unix中的任意脚本?

我想要一个守护进程 ,可以将一个任意的通用脚本或命令变成一个守护进程 。

有两个我想处理的常见情况:

  1. 我有一个应该永远运行的脚本。 如果它死了(或重新启动),重新启动它。 不要让一次运行两个副本(检测副本是否已经运行,在这种情况下不要启动副本)。

  2. 我有一个简单的脚本或命令行命令,我想永远反复执行(在运行之间短暂的停顿)。 同样,不要让脚本的两个副本同时运行。

当然,在案例2中编写一个围绕脚本的“while(true)”循环,然后为案例1应用一个解决scheme,这是微不足道的,但是一个更一般的解决scheme将直接解决案例2,因为它适用于案例1中的脚本好吧(如果脚本不打算永远不会死的话,你可能只需要一个短暂的停顿或不停顿(当然如果脚本真的不会死的话那么停顿实际上并不重要))。

请注意,该解决scheme不应该涉及,例如,添加文件locking代码或PIDlogging到现有的脚本。

更具体地说,我想要一个我可以运行的程序“daemonize”

% daemonize myscript arg1 arg2 

或者,例如,

 % daemonize 'echo `date` >> /tmp/times.txt' 

这将保持越来越多的date列表附加到times.txt。 (注意,如果守护进程的参数是一个像上面第一种情况一样运行的脚本,那么守护进程仍然会做正确的事情,必要时重新启动它。)然后,我可以在我的.login中放置一个类似上面的命令和/或cron它每小时或每分钟(取决于我有多么担心它意外死亡)。

注意:守护进程脚本将需要记住它是守护进程的命令string,以便如果同一个命令string被再次守护进程,它不会启动第二个副本。

此外,该解决scheme应理想的工作在OS X和Linux,但解决scheme之一是受欢迎的。

编辑:这很好,如果你不得不用sudo daemonize myscript myargs来调用它。

(如果我想到这一切都是错误的,或者有一些快速而肮脏的部分解决scheme,我也很想听到。)


PS:万一它有用, 这里是一个类似于python的问题。

对类似问题的这个答案似乎是一个对任意脚本进行快速而邪恶的妖魔化的有用成语:

您可以使用nohup和&运算符来守护Unix中的任何可执行文件:

 nohup yourScript.sh script args& 

nohup命令允许你closures你的shell会话而不会终止你的脚本,而&将你的脚本放在后台,所以你得到一个shell提示符来继续你的会话。 唯一的小问题是标准输出和标准错误都发送到./nohup.out,所以如果你在这个庄园里启动几个脚本,他们的输出将会交织在一起。 更好的命令是:

 nohup yourScript.sh script args >script.out 2>script.error& 

这将标准输出到您select的文件和标准错误到您select的不同文件。 如果你只想使用一个文件来标准输出和标准错误,你可以这样做:

 nohup yourScript.sh script args >script.out 2>&1 & 

2>&1告诉shell将标准错误(文件描述符2)redirect到与标准输出(文件描述符1)相同的文件。

要运行命令只有一次,如果它死了,重新启动它,你可以使用这个脚本:

 #!/bin/bash if [[ $# < 1 ]]; then echo "Name of pid file not given." exit fi # Get the pid file's name. PIDFILE=$1 shift if [[ $# < 1 ]]; then echo "No command given." exit fi echo "Checking pid in file $PIDFILE." #Check to see if process running. PID=$(cat $PIDFILE 2>/dev/null) if [[ $? = 0 ]]; then ps -p $PID >/dev/null 2>&1 if [[ $? = 0 ]]; then echo "Command $1 already running." exit fi fi # Write our pid to file. echo $$ >$PIDFILE # Get command. COMMAND=$1 shift # Run command until we're killed. while true; do $COMMAND "$@" sleep 10 # if command dies immediately, don't go into un-ctrl-c-able loop done 

第一个参数是要使用的pid文件的名称。 第二个参数是命令。 其他所有参数都是命令的参数。

如果你把这个脚本命名为restart.sh,你可以这么称呼它:

 nohup restart.sh pidFileName yourScript.sh script args >script.out 2>&1 & 

我为长答案道歉(请参阅我的答案如何指定规范的意见)。 我正在努力做到全面,所以你尽可能做到尽善尽美。 🙂

如果你能够安装程序(具有root访问权限),并且愿意做一次性的脚步工作来为守护进程执行设置你的脚本(比起简单地指定在命令行上运行的命令行参数,但每个服务只需要完成一次),我有一个更强大的方法。

它涉及到使用守护进程 。 这篇文章的其余部分介绍了如何使用daemontools来设置服务。

初始设置

  1. 按照如何安装守护进程的说明进行操作。 一些发行版(例如Debian,Ubuntu)已经有了它的包,所以就使用它。
  2. 创build一个名为/service的目录。 安装程序应该已经这样做了,但只需validation,或者手动安装。 如果你不喜欢这个位置,你可以在你的svscanboot脚本中改变它,虽然大多数守护程序用户习惯使用/service ,如果你不使用它,会感到困惑。
  3. 如果您使用的是Ubuntu或其他不使用标准init发行版(即不使用/etc/inittab ),则需要使用预先安装的inittab作为安排svscanboot的基础,以便由init调用。 这并不难,但是你需要知道如何configuration你的操作系统使用的initsvscanboot是一个调用svscan的脚本,它执行寻找服务的主要工作; 它是从init调用的,所以init会安排在任何原因死亡时重新启动它。

每项服务设置

  1. 每个服务都需要一个服务目录 ,存储关于服务的pipe理信息。 你也可以build立一个位置来放置这些服务目录,这样它们就在同一个地方。 通常我使用/var/lib/svscan ,但是任何新的位置都可以。
  2. 我通常使用脚本来设置服务目录,以节省大量的手动重复工作。 例如,

     sudo mkservice -d /var/lib/svscan/some-service-name -l -u user -L loguser "command line here" 

    其中some-service-name是您想要提供服务的名称, user是运行该服务的用户,而loguser是运行logger的用户。 (logging只是一点点解释。)

  3. 您的服务必须在前台运行。 如果您的程序背景默认,但有一个选项来禁用,那么这样做。 如果你的程序背景没有办法禁用它,请阅读fghack ,虽然这是一个折衷:你不能再使用svc控制程序。
  4. 编辑run脚本以确保它正在做你想做的事情。 如果您希望您的服务经常退出,则可能需要在顶部放置sleep呼叫。
  5. 当一切正确设置好后,在/service创build一个指向你的服务目录的符号链接。 (不要把服务目录直接放在/service ,这样就很难从svscan的手表中删除服务。)

logging

  1. 守护进程的日志logging方式是让服务将日志消息写入标准输出(如果使用的是使用mkservice生成的脚本,则为标准错误); svscan负责将日志消息发送到日志服务。
  2. 日志logging服务从标准input中获取日志消息。 由mkservice生成的日志logging服务脚本将在log/main目录中创build自动旋转的时间戳日志文件。 当前的日志文件被称为current
  3. 日志服务可以独立于主服务启动和停止。
  4. 通过tai64nlocal日志文件将把时间戳转换成人可读的格式。 (TAI64N是一个具有纳秒计数的64位primefaces时间戳。)

控制服务

  1. 使用svstat获取服务的状态。 请注意,日志服务是独立的,并具有自己的状态。
  2. 您可以使用svc控制您的服务(启动,停止,重新启动等)。 例如,要重新启动服务,请使用svc -t /service/some-service-name ; -t表示“发送SIGTERM ”。
  3. 其他可用信号包括-hSIGHUP ), -aSIGALRM ),- -1SIGUSR1 ),- -2SIGUSR2 )和-kSIGKILL )。
  4. 要停止服务,请使用-d 。 您还可以通过在服务目录中创build一个名为down的文件down防止服务在启动时自动启动。
  5. 要启动该服务,请使用-u 。 这是不必要的,除非您之前已经closures它(或者将其设置为不自动启动)。
  6. 要请主pipe退出,请使用-x ; 通常与-d一起使用来终止服务。 这是允许删除服务的常用方法,但是您必须首先取消服务与/service链接,否则svscan将重新启动pipe理员。 此外,如果您使用日志服务( mkservice -l )创build服务,请记住在删除服务目录之前还要退出日志pipe理器(例如, svc -dx /var/lib/svscan/some-service-name/log ) 。

概要

优点:

  1. 守护进程提供了一个防弹的方式来创build和pipe理服务。 我把它用于我的服务器,我强烈推荐它。
  2. 它的日志系统非常强大,服务自动重启设备也是如此。
  3. 因为它使用您编写/调整的shell脚本启动服务,所以您可以根据需要定制服务。
  4. 强大的服务控制工具:您可以将大部分信号发送到服务,并可以可靠地上下服务。
  5. 您的服务可以保证一个干净的执行环境:它们将按照init提供的相同环境,进程限制等执行。

缺点:

  1. 每个服务需要一些设置。 谢天谢地,这只需要每个服务一次。
  2. 服务必须设置为在前台运行。 此外,为了获得最佳结果,应设置它们以logging到标准输出/标准错误,而不是syslog或其他文件。
  3. 如果您对守护进程的做事方式不熟悉,那么学习曲线就会陡峭。 您必须使用svc重新启动服务,并且不能直接运行脚本(因为它们不在监控程序的控制之下)。
  4. 许多家政文件,和许多家务pipe理过程。 每个服务都需要自己的服务目录,每个服务使用一个pipe理程序进程自动重启服务。 (如果你有很多服务,你会在你的stream程表中看到很多 supervisestream程。)

总的来说,我认为daemontools是一个很好的系统,可以满足您的需求。 我欢迎任何有关如何设置和维护的问题。

你应该看看守护进程 。 它允许检测第二个副本(但它使用文件locking机制)。 它也适用于不同的UNIX和Linux发行版。

如果您需要自动启动您的应用程序作为守护进程,那么您需要创build适当的init脚本。

您可以使用以下模板:

 #!/bin/sh # # mydaemon This shell script takes care of starting and stopping # the <mydaemon> # # Source function library . /etc/rc.d/init.d/functions # Do preliminary checks here, if any #### START of preliminary checks ######### ##### END of preliminary checks ####### # Handle manual control parameters like start, stop, status, restart, etc. case "$1" in start) # Start daemons. echo -n $"Starting <mydaemon> daemon: " echo daemon <mydaemon> echo ;; stop) # Stop daemons. echo -n $"Shutting down <mydaemon>: " killproc <mydaemon> echo # Do clean-up works here like removing pid files from /var/run, etc. ;; status) status <mydaemon> ;; restart) $0 stop $0 start ;; *) echo $"Usage: $0 {start|stop|status|restart}" exit 1 esac exit 0 

我想你可能想尝试start-stop-daemon(8) 。 查看任何Linux发行版中的/etc/init.d中的脚本以查看示例。 它可以通过命令行调用或PID文件find启动的进程,所以它可以满足您的所有需求,除了脚本的监视程序。 但是,您可以随时启动另一个守护程序看守程序脚本,在必要时重新启动脚本。

作为已经提到的守护进程和守护进程的替代scheme,libslack包中有daemon命令。

daemon是相当可configuration的,并关心所有繁琐的守护进程的东西,如自动重启,日志logging或pidfile处理。

如果你正在使用OS X,我build议你看看launchd是如何工作的。 它会自动检查以确保您的脚本正在运行,并在必要时重新启动它。 它还包括各种调度function等。它应该同时满足要求1和要求2。

至于确保您的脚本只能运行一个副本,您需要使用一个PID文件。 通常,我将一个文件写入/var/run/.pid,其中包含当前正在运行的实例的PID。 如果程序运行时文件存在,它会检查文件中的PID是否真正在运行(程序可能已经崩溃或者忘记删除PID文件)。 如果是,则中止。 如果没有,开始运行并覆盖PID文件。

Daemontools( http://cr.yp.to/daemontools.html )是由dj bernstein编写的一套非常实用的工具。 我用这个成功了一些。 讨厌的部分是,当你运行脚本时,没有一个脚本返回任何可见的结果 – 只是看不见的返回码。 但一旦运行,它是防弹的。

首先从http://code.activestate.com/recipes/278731/获取;createDaemon()

那么主要的代码是:

 import subprocess import time createDaemon() while True: subprocess.call(" ".join(sys.argv[1:]),shell=True) time.sleep(10) 

这是一个完整的工作版本,可以将其拷贝到一个空目录中,然后尝试安装CPAN依赖项( Getopt :: Long , File :: Spec , File :: Pid和IPC :: System: :简单 – 所有漂亮的标准,强烈build议任何黑客:你可以一次安装所有的cpan <modulename> <modulename> ... )。


keepAlive.pl:

 #!/usr/bin/perl # Usage: # 1. put this in your crontab, to run every minute: # keepAlive.pl --pidfile=<pidfile> --command=<executable> <arguments> # 2. put this code somewhere near the beginning of your script, # where $pidfile is the same value as used in the cron job above: # use File::Pid; # File::Pid->new({file => $pidfile})->write; # if you want to stop your program from restarting, you must first disable the # cron job, then manually stop your script. There is no need to clean up the # pidfile; it will be cleaned up automatically when you next call # keepAlive.pl. use strict; use warnings; use Getopt::Long; use File::Spec; use File::Pid; use IPC::System::Simple qw(system); my ($pid_file, $command); GetOptions("pidfile=s" => \$pid_file, "command=s" => \$command) or print "Usage: $0 --pidfile=<pidfile> --command=<executable> <arguments>\n", exit; my @arguments = @ARGV; # check if process is still running my $pid_obj = File::Pid->new({file => $pid_file}); if ($pid_obj->running()) { # process is still running; nothing to do! exit 0; } # no? restart it print "Pid " . $pid_obj->pid . " no longer running; restarting $command @arguments\n"; system($command, @arguments); 

example.pl:

 #!/usr/bin/perl use strict; use warnings; use File::Pid; File::Pid->new({file => "pidfile"})->write; print "$0 got arguments: @ARGV\n"; 

现在你可以使用: ./keepAlive.pl --pidfile=pidfile --command=./example.pl 1 2 3来调用上面的例子,文件pidfile将被创build,你将看到输出:

 Pid <random number here> no longer running; restarting ./example.pl 1 2 3 ./example.pl got arguments: 1 2 3 

你也可以试试Monit 。 Monit是一项监视和报告其他服务的服务。 虽然它主要用作通知(通过电子邮件和短信)运行时问题的方式,但它也可以做到这里提到的大多数其他build议。 它可以自动(重新)启动和停止程序,发送电子邮件,启动其他脚本,并维护您可以select的输出日志。 另外,我发现安装和维护起来很简单,因为这里有可靠的文档。

你可以尝试不朽它是一个* nix跨平台(操作系统不可知论)的主pipe。

要快速尝试macOS:

 brew install immortal 

如果您使用的是从端口或通过使用pkg的FreeBSD :

 pkg install immortal 

对于Linux下载预编译的二进制文件或从源代码: https : //immortal.run/source/

你可以像这样使用它:

 immortal -l /var/log/date.log date 

或者通过configurationYAML文件给你更多的select,例如:

 cmd: date log: file: /var/log/date.log age: 86400 # seconds num: 7 # int size: 1 # MegaBytes timestamp: true # will add timesamp to log 

如果你想保持标准的错误输出在一个单独的文件,你可以使用类似于:

 cmd: date log: file: /var/log/date.log age: 86400 # seconds num: 7 # int size: 1 # MegaBytes stderr: file: /var/log/date-error.log age: 86400 # seconds num: 7 # int size: 1 # MegaBytes timestamp: true # will add timesamp to log 

我对另一个答案做了一系列的改进。

  1. 这个脚本的标准输出纯粹是来自它的subprocess的标准输出,除非由于检测到命令已经运行而退出
  2. 清除pidfile后终止
  3. 可选的可configuration的超时期限(接受任何肯定的数字参数,发送到sleep
  4. 使用提示符-h
  5. 任意命令执行,而不是单个命令执行。 最后一个arg或其余的args(如果多于一个最后一个arg)被发送到eval ,所以你可以构造任何types的shell脚本作为一个string发送到这个脚本作为最后一个arg(或尾随args)为了守护进程
  6. 参数计数比较使用-lt而不是<

这是脚本:

 #!/bin/sh # this script builds a mini-daemon, which isn't a real daemon because it # should die when the owning terminal dies, but what makes it useful is # that it will restart the command given to it when it completes, with a # configurable timeout period elapsing before doing so. if [ "$1" = '-h' ]; then echo "timeout defaults to 1 sec.\nUsage: $(basename "$0") sentinel-pidfile [timeout] command [command arg [more command args...]]" exit fi if [ $# -lt 2 ]; then echo "No command given." exit fi PIDFILE=$1 shift TIMEOUT=1 if [[ $1 =~ ^[0-9]+(\.[0-9]+)?$ ]]; then TIMEOUT=$1 [ $# -lt 2 ] && echo "No command given (timeout was given)." && exit shift fi echo "Checking pid in file ${PIDFILE}." >&2 #Check to see if process running. if [ -f "$PIDFILE" ]; then PID=$(< $PIDFILE) if [ $? = 0 ]; then ps -p $PID >/dev/null 2>&1 if [ $? = 0 ]; then echo "This script is (probably) already running as PID ${PID}." exit fi fi fi # Write our pid to file. echo $$ >$PIDFILE cleanup() { rm $PIDFILE } trap cleanup EXIT # Run command until we're killed. while true; do eval "$@" echo "I am $$ and my child has exited; restart in ${TIMEOUT}s" >&2 sleep $TIMEOUT done 

用法:

 $ term-daemonize.sh pidfilefortesting 0.5 'echo abcd | sed s/b/zzz/' Checking pid in file pidfilefortesting. azzzcd I am 79281 and my child has exited; restart in 0.5s azzzcd I am 79281 and my child has exited; restart in 0.5s azzzcd I am 79281 and my child has exited; restart in 0.5s ^C $ term-daemonize.sh pidfilefortesting 0.5 'echo abcd | sed s/b/zzz/' 2>/dev/null azzzcd azzzcd azzzcd ^C 

请注意,如果您从不同目录运行此脚本,它可能会使用不同的pidfiles,并且不会检测到任何现有的运行实例。 由于它是为了运行和重启通过参数提供的短暂命令而devise的,因此无法知道是否已经启动了某个命令 ,因为谁会说是否是同一个命令呢? 为了改进这种只执行一个事例的强制执行,需要一个特定于这种情况的解决scheme。

另外,为了使它作为一个适当的守护进程,你必须使用(在最低限度)nohup作为其他答案提到。 我没有努力提供任何恢复能力的过程中可能会收到的信号。

还有一点需要注意的是,杀死这个剧本(如果是从另外一个死亡的剧本或者一个信号中被调用的话)可能不会成功杀死那个孩子,特别是如果这个孩子是另一个剧本的话。 我不确定这是为什么,但这似乎与eval作用方式有关,这对我来说是神秘的。 因此,可以谨慎的做法是用另一个答案中只接受一个命令的东西replace该行。