Shell的后台运行(&)与nohup

shell的后台运行(&)与nohup

在shell脚本中当我们需要把一个任务放在后台运行时,通常我们会使用&符号:

subcommand &

此时主进程会继续往下执行,而子进程会在后台启动运行。

于此同时,我们常会看到nohup会和后台任务一起使用,格式是:

nohup subcommand &

nohup在这里起了什么角色呢。

nohup

nohup起两个作用:

  • 正如名字所声称的,忽略所有发送给子命令的挂断(SIGHUP)信号
  • nohup subcommand &
    

    这样所有发给subcommand的SIGHUP信号都被忽略,subcommand就不会收到SIGHUP信号。

    什么是SIGHUP信号呢?
    简单的理解可以是终端结束时,操作系统会发送SIGHUP信号到后台进程。

  • 重定向子命令的标准输出(stdout)和标准错误(stderr)
    我们可以在终端看到如下输出:
  • nohup: appending output to "nohup.out"
    

    表示subcommand的标准输出和标准错误被重定向到nohup.out文件;如果没有使用nohup方式,则subcommand的标准输出和标准错误是复用父进程的标准输出和标准错误。

    例子1:后台运行(&)

    $ cat testshell0.sh 
    #!/bin/bash
    #trap "echo \"SIGHUP is received\"" 1
    for i in {1..10000}; do
           echo "$i: in $0"
      1>&2 echo "$i: in $0"
      sleep 1
    $ cat testshell1.sh  
    #!/bin/bash
    ./testshell0.sh &
    for i in {1..10000}; do
      echo "$i: in $0"
      sleep 1
    $ ./testshell1.sh 
    1: in ./testshell1.sh
    1: in ./testshell0.sh
    1: in ./testshell0.sh
    2: in ./testshell1.sh
    2: in ./testshell0.sh
    2: in ./testshell0.sh
    CTRL-C(^C)
    $ 3: in ./testshell0.sh
    3: in ./testshell0.sh
    4: in ./testshell0.sh
    5: in ./testshell0.sh
    

    脚本testshell1.sh以后台方式(&)调用testshell0.sh;
    我们看到testshell1.sh和testshell0.sh的输出都在屏幕上,而当(CTRL-C)杀掉testshell1.sh的时候,testshell0.sh继续在运行,继续往屏幕打印输出。

    例子2:nohup方式后台运行(&)

    $ cat testshell0.sh 
    #!/bin/bash
    #trap "echo \"SIGHUP is received\"" 1
    for i in {1..10000}; do
           echo "$i: in $0"
      1>&2 echo "$i: in $0"
      sleep 1
    $ cat testshell1.sh  
    #!/bin/bash
    nohup ./testshell0.sh &
    for i in {1..10000}; do
      echo "$i: in $0"
      sleep 1
    $ ./testshell1.sh 
    nohup: appending output to "nohup.out"
    1: in ./testshell1.sh
    1: in ./testshell0.sh
    1: in ./testshell0.sh
    2: in ./testshell1.sh
    2: in ./testshell0.sh
    2: in ./testshell0.sh
    CTRL-C(^C)
    

    脚本testshell1.sh以nohup的方式在后台(&)调用testshell0.sh;
    我们看到testshell1.sh的输出在屏幕上,testshell0.sh的输出在文件nohup.out里:

    $ tail -f nohup.out
    1: in ./testshell0.sh
    1: in ./testshell0.sh
    2: in ./testshell0.sh
    2: in ./testshell0.sh
    3: in ./testshell0.sh
    3: in ./testshell0.sh
    

    而当(CTRL-C)杀掉testshell1.sh的时候,testshell0.sh继续在运行,继续往nohup.log里面打印输出。

    在这两个例子中,当testshell1.sh已经停止时,testshell0.sh并不会结束,而都能继续运行。

    例子3:后台运行(&) + 关闭终端

    代码和运行方法同例子1,只是在运行时关闭终端。
    结果testshell1.sh和testshell0.sh都结束了。

    $ ps -ef | grep testshell | grep -v grep
    

    例子4:nohup方式后台运行(&) + 关闭终端

    代码和运行方法同例子2,只是在运行时关闭终端。
    结果testshell1.sh结束了,而testshell0.sh还在继续运行。

    $ ps -ef | grep testshell | grep -v grep
    <uid>  <pid>     1  0 22:29 ?        00:00:00 /bin/bash ./testshell0.sh
    

    需要注意的是,此时testshell0.sh的父进程变成了进程号1,而不是testshell1.sh,因为testshell1.sh已经死了,操作系统接管了testshell1.sh进程。

    对比例子3和例子4,差别是是否以nohup的方式运行testshell0.sh,在例子3不是以nohup的方式,这样当终端结束的时候,testshell0.sh会收到SIGHUP信号,缺省的处理方式是杀掉自己,所以testshell0.sh也死了;而例子4使用了nohup方式,他会忽略SIGHUP信号,所以testshell0.sh继续运行。

    看例子5,注意其中的区别

    例子5:后台运行(&) + 关闭父进程+关闭终端

    代码和运行方法同例子1。
    启动进程testshell1.sh

    $ ./testshell1.sh 
    1: in ./testshell1.sh
    1: in ./testshell0.sh
    1: in ./testshell0.sh
    2: in ./testshell1.sh
    2: in ./testshell0.sh
    2: in ./testshell0.sh
    3: in ./testshell1.sh
    3: in ./testshell0.sh
    3: in ./testshell0.sh
    

    此时testshell1.sh和testshell0.sh同时在运行,往终端打印输出。

    $ ps -ef | grep testshell | grep -v grep
    <uid>  13789 13642  0 22:34 pts/10   00:00:00 /bin/bash ./testshell1.sh
    <uid>  13790 13789  0 22:34 pts/10   00:00:00 /bin/bash ./testshell0.sh
    

    杀掉进程testshell1.sh
    屏幕继续打印testshell0.sh的输出。

    CTRL-C(^C)
    $ 4: in ./testshell0.sh
    4: in ./testshell0.sh
    5: in ./testshell0.sh
    5: in ./testshell0.sh
    

    查看进程状态

    $ ps -ef | grep testshell | grep -v grep
    <uid>  13790     1  0 22:34 pts/10   00:00:00 /bin/bash ./testshell0.sh
    

    子进程testshell0.sh继续在运行。

    此时我们退出终端,再查看进程状态

    $ ps -ef | grep testshell | grep -v grep
    <uid>  13790     1  0 22:34 pts/10   00:00:00 /bin/bash ./testshell0.sh