为什么 & 挂后台还不够
很多同学在 SSH 里跑长任务,习惯在命令末尾加一个 & 把进程丢到后台。但只要这个 SSH 终端一关,任务往往跟着一起没了。原因是:终端关闭时,内核会向该终端的前台进程组发送 SIGHUP(挂断信号),会话内的进程收到后默认动作就是退出。& 只是把进程从”前台”挪到”后台”,并没有让它脱离这个会话,自然躲不过 SIGHUP。
nohup 到底做了什么
nohup 的全称是 no hang up,它做了一件很克制的事:让被它启动的进程忽略 SIGHUP 信号。这样即使终端关闭、会话被挂断,进程也不会被这个信号杀掉。需要注意的是,nohup 本身并不负责”后台运行”,它只负责”免疫挂断”,所以真正稳妥的写法是 nohup 与 & 搭配使用。
标准用法
最常用的一行命令:
nohup python3 train.py > train.log 2>&1 &
拆开看每一段的作用:
nohup:让 train.py 忽略 SIGHUP,终端关闭不受影响。> train.log:把标准输出重定向到文件。若不重定向,nohup 会默认写到当前目录的nohup.out,日志一多很难定位。2>&1:把标准错误也并入标准输出,同一个文件里既能看正常日志也能看报错。- 结尾的
&:把整条命令放进后台,SSH 终端立刻恢复可用。
启动后终端会打印一个进程号(PID),建议顺手记下来,后续停止任务要用:
[1] 28641
找回与查看后台任务
如果换了个终端、或者忘了 PID,可以用进程名反查:
ps -ef | grep train.py
确认进程还在,再实时跟踪日志:
tail -f train.log
这里有个常见误区:nohup 启动的任务,在新的 SSH 会话里用 jobs 是看不到的,因为 jobs 只显示当前 shell 自己管理的作业。跨会话查找只能靠 ps 或按 PID。
停止后台任务
拿到 PID 后正常终止:
kill 28641
如果进程卡住不响应,再用强制信号兜底:
kill -9 28641
能优雅退出就别急着上 -9,强杀可能让正在写数据库或写文件的任务留下半成品。
nohup、setsid、tmux 怎么选
- nohup + &:一次性长任务(跑批、训练、大文件处理),启动后不需要交互,最简单。
- setsid:让进程直接开一个全新会话、彻底脱离当前控制终端,比 nohup 更”干净”,适合脚本里想彻底脱离的场景。
- tmux / screen:任务需要你随时接回去继续操作(比如交互式调试、边跑边看),断线后能重新 attach。nohup 做不到”接回交互界面”,这点要分清楚。
三个容易踩的坑
- 以为加了 nohup 就不用 &:只 nohup 不加 &,进程仍在前台占着终端,你还会以为它卡住了。两者职责不同,长任务建议一起用。
- 忘了重定向日志:默认写
nohup.out,多个任务混在一个目录时日志会互相覆盖或难以区分,务必显式指定日志文件名。 - 指望 nohup 保证开机自启:nohup 只解决”当前这次运行不被终端关闭杀掉”,服务器重启后并不会自动拉起。要开机自启,应该写成 systemd 服务。
小结
记住一句话:nohup 管”不被挂断杀死”,& 管”放到后台”,重定向管”日志去哪”。三者组合起来,才是 SSH 里跑长任务的完整姿势;需要断线重连继续交互,就换 tmux;需要开机自启,就交给 systemd。