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

发布于 2024-09-13 16:18:07 字数 226 浏览 8 评论 0原文

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

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

while true; do
  sleep 1000
done

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

I'm wondering if there is a better way to make a daemon that waits for something using only sh than:

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

while true; do
  sleep 1000
done

In particular, I'm wondering if there's any way to get rid of the loop and still have the thing listen for the signals.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

奈何桥上唱咆哮 2024-09-20 16:18:07

仅将脚本置于后台 (./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 &

重定向解释(请参阅 bash 重定向

  • 0<&- 关闭标准输入
  • &> file 将 stdout 和 stderr 发送到文件

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

  • 您仍然有一个对脚本打开的文件描述符,这意味着它所安装的目录将是不可安装的。要成为真正的守护进程,您应该 chdir("/") (或脚本中的 cd /),并 fork 以便父进程退出,因此原始描述符是关闭。
  • 也许运行umask 0。您可能不想依赖守护程序调用者的 umask。

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

Just backgrounding your script (./myscript &) will not daemonize it. See http://www.faqs.org/faqs/unix-faq/programmer/faq/, section 1.7, which describes what's necessary to become a daemon. You must disconnect it from the terminal so that SIGHUP does not kill it. You can take a shortcut to make a script appear to act like a daemon;

nohup ./myscript 0<&- &>/dev/null &

will do the job. Or, to capture both stderr and stdout to a file:

nohup ./myscript 0<&- &> my.admin.log.file &

Redirection explained (see bash redirection)

  • 0<&- closes stdin
  • &> file sends stdout and stderr to a file

However, there may be further important aspects that you need to consider. For example:

  • You will still have a file descriptor open to the script, which means that the directory it's mounted in would be unmountable. To be a true daemon you should chdir("/") (or cd / inside your script), and fork so that the parent exits, and thus the original descriptor is closed.
  • Perhaps run umask 0. You may not want to depend on the umask of the caller of the daemon.

For an example of a script that takes all of these aspects into account, see Mike S' answer.

浪漫之都 2024-09-20 16:18:07

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

这个http://www.faqs.org/faqs/unix-faq/programmer /faq/ 描述了成为守护进程所需的条件。这个将bash脚本作为守护进程运行实现了setsid,尽管它错过了root的chdir 。

原始发帖者的问题实际上比“如何使用 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 padawan." >/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

Some of the top-upvoted answers here are missing some important parts of what makes a daemon a daemon, as opposed to just a background process, or a background process detached from a shell.

This http://www.faqs.org/faqs/unix-faq/programmer/faq/ describes what is necessary to be a daemon. And this Run bash script as daemon implements the setsid, though it misses the chdir to root.

The original poster's question was actually more specific than "How do I create a daemon process using bash?", but since the subject and answers discuss daemonizing shell scripts generally, I think it's important to point it out (for interlopers like me looking into the fine details of creating a daemon).

Here's my rendition of a shell script that would behave according to the FAQ. Set DEBUG to true to see pretty output (but it also exits immediately rather than looping endlessly):

#!/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 padawan." >/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.

Output looks like this when DEBUG is set to true. Notice how the session and process group ID (SESS, PGID) numbers change:

<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
执手闯天涯 2024-09-20 16:18:07
# double background your script to have it detach from the tty
# cf. http://www.linux-mag.com/id/5981 
(./program.sh &) & 
# double background your script to have it detach from the tty
# cf. http://www.linux-mag.com/id/5981 
(./program.sh &) & 
苏佲洛 2024-09-20 16:18:07

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

否则,是的,某个地方一定有一个循环。

Use your system's daemon facility, such as start-stop-daemon.

Otherwise, yes, there has to be a loop somewhere.

鱼窥荷 2024-09-20 16:18:07

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

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

吝吻 2024-09-20 16:18:07

这实际上取决于二进制文件本身要做什么。

例如我想创建一些监听器。

启动守护进程是简单的任务:

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

这是你可以做的简单监听器,它会监听你的声音
来自远程计算机的命令并在本地执行它们:

listener:

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

希望这会有所帮助。

It really depends on what is the binary itself going to do.

For example I want to create some listener.

The starting Daemon is simple task :

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

this is how we start the daemon (common way for all /etc/init.d/ staff)

now as for the listener it self,
It must be some kind of loop/alert or else that will trigger the script
to do what u want. For example if u want your script to sleep 10 min
and wake up and ask you how you are doing u will do this with the

while true ; do sleep 600 ; echo "How are u ? " ; done

Here is the simple listener that u can do that will listen for your
commands from remote machine and execute them on local :

listener :

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

So to start UP it : /tmp/deamon_test/listener start

and to send commands from shell (or wrap it to script) :

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)

Hope this will help.

狼性发作 2024-09-20 16:18:07

以下是对在 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 已被捕获,则执行此操作。

我想没有比这更简单的了。

Here is the minimal change to the original proposal to create a valid daemon in Bourne shell (or 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

Explanation:

  • Line 2-7: A daemon must be forked so it doesn't have a parent. Using an artificial argument to prevent endless forking. "setsid" detaches from starting process and terminal.
  • Line 9: Our desired signal needs to be differentiated from other signals.
  • Line 10: Cleanup is required to get rid of dangling "sleep" processes.
  • Line 11-13: Redirect stdout, stderr and stdin of the script.
  • Line 16: sleep in the background
  • Line 18: wait waits for end of sleep, but gets interrupted by (some) signals.
  • Line 19: Kill sleep process, because that is still running when signal is caught.
  • Line 22: Do the work if SIGUSR1 has been caught.

Guess it does not get any simpler than that.

海夕 2024-09-20 16:18:07

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

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

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

Have a look at the daemon tool from the libslack package:

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

On Mac OS X use a launchd script for shell daemon.

唱一曲作罢 2024-09-20 16:18:07

如果我有一个 script.sh 并且我想从 bash 执行它并让它运行,即使我想关闭我的 bash 会话,那么我会结合 nohup& 在最后。

示例:nohup ./script.sh <输入文件.txt > ./logFile 2>&1 &

inputFile.txt 可以是任何文件。如果您的文件没有输入,那么我们通常使用/dev/null。所以命令是:

nohup ./script.sh nohup ./script.sh nohup ./script.sh /dev/null > ./logFile 2>&1 &

关闭 bash 会话后,打开另一个终端并执行: ps -aux | egrep "script.sh" 你会看到你的脚本仍在后台运行。当然,如果您想停止它,请执行相同的命令 (ps) 和 kill -9

If I had a script.sh and i wanted to execute it from bash and leave it running even when I want to close my bash session then I would combine nohup and & at the end.

example: nohup ./script.sh < inputFile.txt > ./logFile 2>&1 &

inputFile.txt can be any file. If your file has no input then we usually use /dev/null. So the command would be:

nohup ./script.sh < /dev/null > ./logFile 2>&1 &

After that close your bash session,open another terminal and execute: ps -aux | egrep "script.sh" and you will see that your script is still running at the background. Of cource,if you want to stop it then execute the same command (ps) and kill -9 <PID-OF-YOUR-SCRIPT>

﹎☆浅夏丿初晴 2024-09-20 16:18:07

请参阅 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

See Bash Service Manager project: https://github.com/reduardo7/bash-service-manager

Implementation example

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

Usage example

$ ./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
尬尬 2024-09-20 16:18:07

Congbin Duo 的答案主要获取正确的元素来正确地守护进程,包括双分叉、分离 stdin/stdout/stderr、重置cwd 和umask,以及运行setsid。然而,它有两个问题:

  • 它有一个竞争条件,如果终端在他的一句后快速退出,则 to-daemonize 进程将永远不会真正运行。例如,以下命令将可靠地执行sleep命令(仅供参考,在 Fedora Workstation 38 v1.6 LiveCD 上测试):

    gnome-terminal -- sh -c '( cd /; umask 0;setsid sleep 123 /dev/null 2>& ;1 & ) &'
    

    据我所知,问题是,如果终端在进程完全守护进程之前退出(即 setsid 已执行该命令),这将破坏守护进程并该命令永远不会真正运行。

  • 双叉和setsid序列未按正确的顺序完成。它应该像 fork; 一样完成。集西德; fork; 而不是 fork;叉; setid;。例如,请参阅“UNIX 守护进程和双分叉”更详细的解释。

据我所知,以下一行代码解决了这些问题:

setsid sh -c 'cd /; umask 0; "$@" </dev/null >/dev/null 2>&1 &' -- your-script.sh arg1 arg2 & wait $!

解释:

  1. [...] & wait $! 是第一个 fork()。我们等待它以避免过早退出并破坏守护进程。

  2. setsid [...] 是对 setsid() 的调用。

  3. sh -c '[...] &' -- your-script.sh arg1 arg2 启动一个 shell 来执行其中的第二个 fork() 操作。

    your-script.sh arg1 arg2 是守护进程的脚本及其参数(不需要转义它们),并将存储在 $@ 变量中.

  4. <代码>cd /;掩码0; "$@" /dev/null 2>&1 执行其余的守护进程(重置 cwd 和 umask、分离 stdin/stdout/stderr),然后调用守护进程。

Congbin Duo's answer mostly gets the right elements to properly daemonize a process, including double forking, detaching stdin/stdout/stderr, resetting the cwd and umask, and running setsid. However, it has two issues:

  • It has a race condition where if the terminal quickly exits after his one-liner, the to-daemonize process will never actually run. For example, the following command will reliably not execute the sleep command (for reference, tested on a Fedora Workstation 38 v1.6 LiveCD):

    gnome-terminal -- sh -c '( cd /; umask 0; setsid sleep 123 </dev/null >/dev/null 2>&1 & ) &'
    

    As far as I can tell, the problem is that if the terminal exits before the process has been fully daemonized (i.e. setsid has exec'd the command), this will break the daemonized process and the command will never actually run.

  • The double fork and setsid sequence is not done in the right order. It should be done like fork; setsid; fork; instead of fork; fork; setsid;. See for example "UNIX daemonization and the double fork" for a more detailed explanation.

The following one-liner, to my best knowledge, fixes those issues:

setsid sh -c 'cd /; umask 0; "$@" </dev/null >/dev/null 2>&1 &' -- your-script.sh arg1 arg2 & wait $!

Explanation:

  1. [...] & wait $! is the first fork(). We wait for it to avoid exiting prematurely and breaking the daemonization process.

  2. setsid [...] is the call to setsid().

  3. sh -c '[...] &' -- your-script.sh arg1 arg2 starts a shell to do the second fork() inside it.

    your-script.sh arg1 arg2 is the script to daemonize and its arguments (you do not need to escape them) and will be stored in the $@ variable.

  4. cd /; umask 0; "$@" </dev/null >/dev/null 2>&1 does the rest of the daemonization (reset cwd and umask, detach stdin/stdout/stderr) and then invokes the daemon.

ゞ花落谁相伴 2024-09-20 16:18:07

与许多答案一样,这不是“真正的”守护进程,而是 nohup 方法的替代方法。

echo "script.sh" | at now

与使用 nohup 有明显的区别。对于一个人来说,一开始就没有脱离父母。此外,“script.sh”不会继承父级的环境。

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

PS 我个人赞成卡洛的答案,因为它似乎是最优雅的,并且可以在终端和内部脚本中使用

Like many answers this one is not a "real" daemonization but rather an alternative to nohup approach.

echo "script.sh" | at now

There are obviously differences from using nohup. For one there is no detaching from the parent in the first place. Also "script.sh" doesn't inherit parent's environment.

By no means this is a better alternative. It is simply a different (and somewhat lazy) way of launching processes in background.

P.S. I personally upvoted carlo's answer as it seems to be the most elegant and works both from terminal and inside scripts

苏璃陌 2024-09-20 16:18:07

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

您可以使用

$. program.sh &

try executing using &
if you save this file as program.sh

you can use

$. program.sh &
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文