Unix / Linux 下 nohup 和 & 的区别

声明:本文首发 简单教程,网址为 https://www.twle.cn/t/332#reply0

就在刚刚回家的路上,被前同事夺命三连 call 呼唤解决一个问题:为啥放在 crontab 里的命令放在 Shell 会进入假死状态?

那我就问了?什么是假死状态? 同事说,就是一直不会执行完毕,占着 Shell,不能做其它事情。

多次沟通后,才知道他写的这个是一个守护进程似的死循环程序,一旦启动除非发生意外,否则不会自己退出。

那肯定,很显然,会占着 Shell 啊。

nohup 和 &

我给的最简单的解决方案,就是按下组合键 CTRL+Z

其次给的是在执行的命令后添加一个 & 符号。 这个是最简单的后台执行模式,而且 & 号之前有无空格都没多大关系,之后有无空格也没关系。

如果只是告诉它这样,肯定是会出问题的,你能猜到么 ?

大家想到的第一个问题,肯定是退出 Shell 进程就会自动关闭。对的,没错,同事后来给我的答复是退出了进程还存在...囧,我一时不知道怎么解释,山高皇帝远,又没法远程和亲自查看。

但是,同事给我的第一个问题是:为啥一直输出日志啊?

我才想起来,忘记告诉它要 >/dev/null 2&>1 重定向日志。

后来又吧啦吧啦告诉它还可以使用 nohup 把要执行的命令扔到后台。

结果呢? 不说还好,一说人家就开问了,这两个命令有啥区别啊 ?

啊 ? 区别?

&nohup 都能把一个任务放在后台运行,但 & 命令会随着退出远程 Shell 而自动停止任务,而 nohup 则会一直继续运行,除非显示杀掉或者重启电脑

等等,这就完了?

我心慌了...只好回来继续查资料了。

Linux 程序生命周期里最重要的三个信号

来,考考大家,我们平时按的最多的两个组合键 CTRL+CCTRL+Z 分别会产生什么信号 ?

当我们退出远程 Shell 时,又会产生什么信号 ?

可以说,Linux 下有很多信号,但真正常用的且能够影响一个程序的信号就只有那么几个,比如

|信号|说明|

|:---|:---|

|SIGINT|发送给前台进程组中的所有进程。常用于终止正在运行的程序,一般由 CTRL+C 组合键触发|

|SIGTSTP|发送给前台进程组中的所有进程,常用于挂起并暂停一个进程,一般由 CTRL+Z 组合键触发

|SIGHUP | 当用户退出 Shell 时,由该 Shell 开启的所有进程都会接收到这个信号,默认动作为终止进程

虽然 SIGINT 和 SIGHUP 的默认动作都一样,但人家是两个不同的信号,一定要牢记,不然等下你会分不清楚 &nohup 的区别的

& 的运行原理

经过上面的解释,想必你应该也清楚了,给任何命令加上 & 就会让该命令在后台执行

比如我们要在当前目录运行一个 Python3 服务器,命令一般如下


[yufei@www.twle.cn ~]$ python3 -mhttp.server 7777

Serving HTTP on 0.0.0.0 port 7777 (http://0.0.0.0:7777/) ...

这时候会发现,这个命令始终没有退出,我们没法继续在这个 Shell 上做其它事情,怎么办呢 ?

一种就是关闭当前 Shell,然后重新开启一个 Shell 执行相同的命令,并在命令的末尾添加 & 符号


[yufei@www.twle.cn ~]$ python3 -mhttp.server 7777 &

[1] 27203

这时候就会看到,我们的命令并没有占据 Shell 而是返回了一个进程号 [1] 27203[1] 表示任务 ID,而 27203 则表示进程号,我们可以使用 ps aux | grep python 查看下刚刚的命令是否仍在运行


[yufei@www.twle.cn ~]$ ps aux | grep python

luojianguo      27221  0.0  0.0  4286184    916 s007  S+  10:04下午  0:00.01 grep python

luojianguo      27203  0.0  0.1  4263348  3384 s007  S    9:56下午  0:00.35 /usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/Resources/Python.app/Contents/MacOS/Python -mhttp.server 7777

看到 27203 了没有,对就是刚刚我们运行的命令。

至于任务 ID [1] ,在 Linux 中,每一个后台运行的程序,都叫一个任务,当在后台运行时,会分配一个任务 ID

我们可以使用 jobs 命令查看所有后台任务


[yufei@www.twle.cn ~]$ jobs

[1]+  Running                python3 -mhttp.server 7777 &

这时候我们狂按下 CTRL+C 键会发现一定作用都没有,人家还是静静的运行着,从某些方面说 CTRL+C 发送的 SIGINT 是发送给所有前台进程的,后台运行的收不到也是自然的现象

然后,我们关闭 Shell,并重新打开 Shell,就会发现刚刚运行的后台任务也关闭了


[yufei@www.twle.cn ~]$ ps aux | grep python

luojianguo      27501  0.0  0.0  4295984    660 s000  U+  10:11下午  0:00.00 grep python

因此,可以说 & 运行的后台任务,会接收 SIGHUP 的信号,也就是对 SIGHUP 信号不免疫。

注意,如果你在本地演示,会发现进程还在,因为,本地 Shell 即使全部退出,Shell 会把这些后台任务交给登陆用户的 shell 去打理

nohup 并不会把程序放到后台执行

注意,很多文章都没有解释清楚,包括上面我的说法也是错的

「 nohup 并不会把程序放在后台运行 」

比如,我们使用下面的命令


[yufei@www.twle.cn ~]$ nohup python3 -mhttp.server 7777

appending output to nohup.out

除了提示已经把标准输出和标准错误输出到 nohup.out 文件外,人家还占据着 Shell 呢

如果你这后按下 CTRL+C 键就会发现程序立刻就退出了


[yufei@www.twle.cn ~]$ nohup python3 -mhttp.server 7777

appending output to nohup.out

^C[yufei@www.twle.cn ~]$

使用 ps aux | grep python 就会看到刚刚运行的程序没了


[yufei@www.twle.cn ~]$ ps aux | grep python

luojianguo      27911  0.0  0.0  4258468    188 s001  U+  10:42下午  0:00.00 grep python

nohup 的真正作用是让程序忽略 SIGHUP 信号

nohup 命令的作用就是让程序忽略 SIGHUP 信号,但这里有一个前提,就是进程首先是在后台运行的,而不是暂停或挂起的

我们演示下一下,使用下面的命令运行一个 nohup


[yufei@www.twle.cn ~]$ nohup python3 -mhttp.server 7777

appending output to nohup.out

然后按下使用 CTRL+Z 命令先挂起当前 nohup 的命令


[yufei@www.twle.cn ~]$ nohup python3 -mhttp.server 7777

appending output to nohup.out

[1]+  Stopped                nohup python3 -mhttp.server 7777

可以看到我们的程序在后台是暂停的,但没有退出,如果我们使用 exit 命令,会提示有停止任务


[yufei@www.twle.cn ~]$ exit

logout

There are stopped jobs.

如果在此输入 exit 就会直接退出 Shell 并且会把所有的已经停止的程序关闭


[yufei@www.twle.cn ~]$ exit

logout

Saving session...

...copying shared history...

...saving history...truncating history files...

...completed.

[进程已完成]

然后我们重新开启一个 Shell 并使用 ps aux 命令,就会什么也看不到


[yufei@www.twle.cn ~]$ ps aux | grep python

luojianguo      28095  0.0  0.0  4295400    936 s000  S+  10:57下午  0:00.01 grep python

结合 nohup 和 &

如果我们过结合两者,比如使用下面的命令


[yufei@www.twle.cn ~]$ nohup python3 -mhttp.server 7777 &

[1] 28097

[yufei@www.twle.cn ~]$ appending output to nohup.out

然后我们运行 exit 命令退出当前 Shell


[yufei@www.twle.cn ~]$ appending output to nohup.out

exit

logout

Saving session...

...copying shared history...

...saving history...truncating history files...

...completed.

打开一个新的 Shell,输入 ps aux | grep python,就会发现之前的程序还存在


luojianguo      28171  0.0  0.0  4295984    668 s000  U+  10:59下午  0:00.00 grep python

luojianguo      28097  0.0  0.3  4271284  12556  ??  S    10:58下午  0:00.26 /usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/Resources/Python.app/Contents/MacOS/Python -mhttp.server 7777

神奇吧

后记

真的很坑,我一直以为这两个命令都是用来把前台程序放到后台的,结果不是

  1. 只有 & 是把前台程序放到后台的

  2. nohup 只是把让正在运行的程序忽略 SIGHUP 信号而已

声明:本文首发 简单教程,网址为 https://www.twle.cn/t/332#reply0

推荐阅读更多精彩内容