Linux/Linux 后台运行脚本

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.logSTDERR > train.log 分别执行,train.log文件被两次打开写入,而标准输出和错误输出将会竞争覆盖,会出现不可控的问题。

最后的&是指后台运行。

当任务被 & 到后执行时,使用exit或者logout正常登出只会结束前台命令,并不会终止后台任务。但如果你直接关闭会话窗口或者意外断网,系统都会向当前会话下的进程发送 SIGHUP 信号。而进程对此信号的默认处理方式是退出执行。具体来说:终端意外、、关闭时,SIGHUP 信号被发送到 session 首进程以及作为 job 提交的进程(即用 & 符号提交的进程)。此时即便你的命令在最后添加了 & ,终端断开连接时仍然会被 SIGHUP 信号中断。

所以最开始的nohup命令可以使得任务忽略 SIGHUP 信号,即使终端断开连接,程序还是会继续执行。

把前台任务转换为后台任务

当运行任务后,使用ctrl+z可以暂停任务。这时使用jobs可以查看任务运行状态,使用bg可以将任务后台运行。

下面的例子时输入yes命令,输入该命令后,会循环打印y,然后ctrl+z暂停任务,使用jobs查看,再使用bg将该命令转为后台运行。

1
2
3
4
5
6
7
8
# 循环打印`y`
yes
# 将任务暂停
ctrl+z
# 查看任务,输出是:
jobs
# 后台运行任务
bg

执行jobs结果如下:


其中前面的数字表示任务编号,目前只有一个任务,处于suspended状态。

bg后面可以加任务编号,默认是将最新的任务挂载到后台任务队列。

先运行命令yes,然后ctrl+z;再运行cat,然后ctrl+zcat命令不加任务参数的作用是阻塞终端,等待你输入字符串并回显相同的字符串。

1
2
3
4
5
6
yes
# 循环打印`y`
yes
# 将任务暂停
ctrl+z
cat

再使用jobs显示如下:


这时有两条命令都是处于suspended状态。如果想让第二条命令后台运行,则执行bg %2

fg是把一个后台运行的任务转到前台运行,用法类似。

如果想把一个正在运行的前台任务转为后台任务,首先执行ctrl+z,然后使用jobs查看任务号,这里假设为 1,再使用bg %1将任务放入后台运行。但此时这个任务还不会忽略 SIGHUP 信号,使用disown -h %1设置该任务忽略 SIGHUP 信号。

总结

  • exitlogout正常登出并不会终止 & 的后台任务,此时的 SIGHUP 信号只会发给前台任务

  • 关闭窗口或断网,前后台任务都会收到 SIGHUP 信号,使用nohup则可以屏蔽此信号,让任务仍不被中断。

  • 进程收到 SIGHUP 信号时默认的操作是退出执行。但我们可以在代码里使用信号捕捉的方法,捕捉或忽略 SIGHUP 信号的处理,这样进程就不会退出了。

参考

评论