How do I daemonize an arbitrary script in unix?

后端 未结 12 951
陌清茗
陌清茗 2020-11-30 16:33

I\'d like a daemonizer that can turn an arbitrary, generic script or command into a daemon.

There are two common cases I\'d like to deal with:

  1. I hav

相关标签:
12条回答
  • 2020-11-30 16:55

    You can daemonize any executable in Unix by using nohup and the & operator:

    nohup yourScript.sh script args&
    

    The nohup command allows you to shut down your shell session without it killing your script, while the & places your script in the background so you get a shell prompt to continue your session. The only minor problem with this is standard out and standard error both get sent to ./nohup.out, so if you start several scripts in this manor their output will be intertwined. A better command would be:

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

    This will send standard out to the file of your choice and standard error to a different file of your choice. If you want to use just one file for both standard out and standard error you can us this:

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

    The 2>&1 tells the shell to redirect standard error (file descriptor 2) to the same file as standard out (file descriptor 1).

    To run a command only once and restart it if it dies you can use this script:

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

    The first argument is the name of the pid file to use. The second argument is the command. And all other arguments are the command's arguments.

    If you name this script restart.sh this is how you would call it:

    nohup restart.sh pidFileName yourScript.sh script args >script.out 2>&1 &
    
    0 讨论(0)
  • 2020-11-30 16:55

    You could give a try to immortal It is a *nix cross-platform (OS agnostic) supervisor.

    For a quick try on macOS:

    brew install immortal
    

    In case you are using FreeBSD from the ports or by using pkg:

    pkg install immortal
    

    For Linux by downloading the precompiled binaries or from source: https://immortal.run/source/

    You can either use it like this:

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

    Or by a configuration YAML file which gives you more options, for example:

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

    If you would like to keep also the standard error output in a separate file you could use something like:

    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
    
    0 讨论(0)
  • 2020-11-30 16:58

    As an alternative to the already mentioned daemonize and daemontools, there is the daemon command of the libslack package.

    daemon is quite configurable and does care about all the tedious daemon stuff such as automatic restart, logging or pidfile handling.

    0 讨论(0)
  • 2020-11-30 17:00

    If you're using OS X specifically, I suggest you take a look at how launchd works. It will automatically check to ensure your script is running and relaunch it if necessary. It also includes all sorts of scheduling features, etc. It should satisfy both requirement 1 and 2.

    As for ensuring only one copy of your script can run, you need to use a PID file. Generally I write a file to /var/run/.pid that contains a PID of the current running instance. if the file exists when the program runs, it checks if the PID in the file is actually running (the program may have crashed or otherwise forgotten to delete the PID file). If it is, abort. If not, start running and overwrite the PID file.

    0 讨论(0)
  • 2020-11-30 17:01

    Daemontools ( http://cr.yp.to/daemontools.html ) is a set of pretty hard-core utilities used to do this, written by dj bernstein. I have used this with some success. The annoying part about it is that none of the scripts return any visible results when you run them - just invisible return codes. But once it's running it's bulletproof.

    0 讨论(0)
  • 2020-11-30 17:04

    I have made a series of improvements on the other answer.

    1. stdout out of this script is purely made up of stdout coming from its child UNLESS it exits due to detecting that the command is already being run
    2. cleans up after its pidfile when terminated
    3. optional configurable timeout period (Accepts any positive numeric argument, sends to sleep)
    4. usage prompt on -h
    5. arbitrary command execution, rather than single command execution. The last arg OR remaining args (if more than one last arg) are sent to eval, so you can construct any sort of shell script as a string to send to this script as a last arg (or trailing args) for it to daemonize
    6. argument count comparisons done with -lt instead of <

    Here is the script:

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

    Usage:

    $ 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
    

    Beware that if you run this script from different directories it may use different pidfiles and not detect any existing running instances. Since it is designed to run and restart ephemeral commands provided through an argument there is no way to know whether something's been already started, because who is to say whether it is the same command or not? To improve on this enforcement of only running a single instance of something, a solution specific to the situation is required.

    Also, for it to function as a proper daemon, you must use (at the bare minimum) nohup as the other answer mentions. I have made no effort to provide any resilience to signals the process may receive.

    One more point to take note of is that killing this script (if it was called from yet another script which is killed, or with a signal) may not succeed in killing the child, especially if the child is yet another script. I am uncertain of why this is, but it seems to be something related to the way eval works, which is mysterious to me. So it may be prudent to replace that line with something that accepts only a single command like in the other answer.

    0 讨论(0)
提交回复
热议问题