Linux/Linux 后台运行脚本
直接运行后台任务
我们经常需要在 Linux 服务器上执行一些耗时比较长的操作,比如深度学习的模型训练。但是我们平时在 Linux 上运行的程序可能需要和用户进行交互,例如允许让用户输入,然后输出结果也打印到交互命令行上。这种方式比较适合运行一些简单的命令。但这种模式的缺点是,一旦当前的交互命令行退出,程序就停止运行了。这就要求我们在与服务器断开连接之后,依然能够在 Linux 后台运行脚本。这里先给出一个比较完美的写法。
假设你的脚本是 train.py
,你想保存的输出文件为 train.log
,那么使用如下语句:
1 | nohup python -u train.py > train.log 2>&1 & |
nohup
命令的意思是 no hang up(不挂起),也就是说,当前交互命令行退出的时候,程序还会执行。
python -u
中的 -u
参数是使得python不启用缓冲。默认情况下代码中的输出语句的内容不会立刻被写入 train.log
,而是先会存储在缓冲区中,等到一定时机再写入train.log
。使用-u
参数会关闭缓冲,输出立刻被写入文件。
> train.log
表示把标准输出 (STDOUT) 重定向到train.log
中。这样,在代码中使用print
打印的字符都会保存在train.log
中。
2>&1
中,1
表示文件描述符 1,表示标准输出,2
表示文件描述符 2,意思是标准错误输出,0
表示标准输入。2>&1
表示标准输出和错误输出合并了,将错误输出重定向到标准输出。因为标准错误输出 (STDERR) 没有缓冲区,而标准输出有。这就会导致STDOUT > train.log
和STDERR > train.log
分别执行,train.log
文件被两次打开写入,而标准输出和错误输出将会竞争覆盖,会出现不可控的问题。
最后的&
是指后台运行。
当任务被 & 到后执行时,使用exit
或者logout
正常登出只会结束前台命令,并不会终止后台任务。但如果你直接关闭会话窗口或者意外断网,系统都会向当前会话下的进程发送 SIGHUP 信号。而进程对此信号的默认处理方式是退出执行。具体来说:终端意外、、关闭时,SIGHUP 信号被发送到 session 首进程以及作为 job 提交的进程(即用 & 符号提交的进程)。此时即便你的命令在最后添加了 & ,终端断开连接时仍然会被 SIGHUP 信号中断。
所以最开始的nohup
命令可以使得任务忽略 SIGHUP 信号,即使终端断开连接,程序还是会继续执行。
把前台任务转换为后台任务
当运行任务后,使用ctrl+z
可以暂停任务。这时使用jobs
可以查看任务运行状态,使用bg
可以将任务后台运行。
下面的例子时输入yes
命令,输入该命令后,会循环打印y
,然后ctrl+z
暂停任务,使用jobs
查看,再使用bg
将该命令转为后台运行。
1 | # 循环打印`y` |
执行jobs
结果如下:

其中前面的数字表示任务编号,目前只有一个任务,处于suspended
状态。
bg
后面可以加任务编号,默认是将最新的任务挂载到后台任务队列。
先运行命令yes
,然后ctrl+z
;再运行cat
,然后ctrl+z
。cat
命令不加任务参数的作用是阻塞终端,等待你输入字符串并回显相同的字符串。
1 | yes |
再使用jobs
显示如下:

这时有两条命令都是处于suspended
状态。如果想让第二条命令后台运行,则执行bg %2
。
fg
是把一个后台运行的任务转到前台运行,用法类似。
如果想把一个正在运行的前台任务转为后台任务,首先执行ctrl+z
,然后使用jobs
查看任务号,这里假设为 1,再使用bg %1
将任务放入后台运行。但此时这个任务还不会忽略 SIGHUP 信号,使用disown -h %1
设置该任务忽略 SIGHUP 信号。
总结
exit
或logout
正常登出并不会终止 & 的后台任务,此时的 SIGHUP 信号只会发给前台任务关闭窗口或断网,前后台任务都会收到 SIGHUP 信号,使用
nohup
则可以屏蔽此信号,让任务仍不被中断。进程收到 SIGHUP 信号时默认的操作是退出执行。但我们可以在代码里使用信号捕捉的方法,捕捉或忽略 SIGHUP 信号的处理,这样进程就不会退出了。
参考