ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux后台任务防杀指南:从、jobs到nohup与disown

Linux后台任务防杀指南:从、jobs到nohup与disown 1. 为什么终端一关后台任务就跟着没了先搞懂进程和终端的关系说个我早年的经历。那时候刚接触 Linux写了个数据同步脚本估摸着要跑半小时我就想“放后台跑吧”于是命令后面加了个然后安心把终端窗口一关去吃午饭了。回来一看数据没同步脚本也找不到了。更气人的是重跑一次关掉终端还是没了。后来才明白这不叫后台这叫“挂后台然后又把它谋杀了”。要搞懂 Job Control第一步不是记命令而是搞清楚进程、终端、会话这三者的关系。这三者的关系搞明白了后面所有命令的行为你都能自己推断出来。1.1 进程是怎么“挂在”终端上的Linux 里每个终端窗口或者叫伪终端打开后都会有一个对应的会话session和终端设备。你在终端里启动的每一个命令正常情况下都会成为这个会话里的一个进程并且这个进程的“标准输入、标准输出、标准错误”都指向这个终端设备。什么意思呢就是但凡它想往屏幕上打印点什么就直接打到你的终端上但凡它想从键盘读取点输入就直接读你敲的字符。这就是“前台进程”的默认状态。这里有个很关键、但很多人忽略的点终端窗口关闭的时候内核会向这个会话的控制进程发送一个 SIGHUP 信号hang up挂断信号。控制进程收到这个信号后默认行为就是终止。更麻烦的是如果这个控制进程是个 shellshell 退出前通常会把这个会话里的其他作业也一并处理掉——这就是你的后台任务“陪葬”的根源。用大白话说终端就是进程的“老家”你关掉终端相当于把房子拆了住在里面的人自然没地方去系统就干脆把它们都清了。1.2 进程组与会话作业控制的地基为了实现对进程的批量管理Linux 有一套进程组和会话的机制每个进程都属于一个进程组进程组有一个组长组内所有进程共享一个进程组 IDPGID。一个或多个进程组组成一个会话会话有一个领头进程通常是 shell 本身。Job Control 说白了就是由 shell 来管理当前会话下的这些进程组把它们看作一个个“作业”然后你可以通过命令让某个作业在前台、后台、暂停三种状态之间切换。举个例子你执行tar -czf backup.tar.gz /data这只是一个进程。但如果你执行的是一个管道命令cat a.log | grep error | wc -l这三个进程会组成一个进程组。shell 会把这一整条管道命令看作一个作业job你可以对这个作业整体进行操作比如一键暂停、一键切到后台。明白了这一点你就能理解为什么kill %1能杀掉一整条管道命令里所有进程而不是只杀其中一个。2. 每个作业到底什么状态jobs 命令输出解读在你开始操作前后台切换之前你得先有个“当前会话里有哪些作业”的清单。这个清单就是jobs命令。我见过不少朋友jobs打出来一片空白就开始怀疑人生以为自己后台任务丢了。其实多半不是而是他开了一个新的终端窗口或者重新连了一次 SSH 会话——作业表是跟着 shell 走的换了 shell自然就看不到了。2.1 三种作业状态前台、后台、已停止一个作业在当前 shell 里只有三种状态前台运行Foreground占着终端你敲什么都进它嘴里shell 在它跑完之前不会给你新的提示符。后台运行Background进程还在跑但 shell 已经还给你提示符了你可以继续敲别的命令只是它的输出仍然会往终端上打。已停止Stopped进程被挂起了暂停执行但进程还在内存里随时可以恢复。这种状态通常由CtrlZ触发也可以被SIGTSTP信号触发。注意一个细节后台运行不等于不占终端输出。你在后台跑了个ping它照样往你的终端上刷64 bytes from ...。你以为它“后台”了就不吵你了实际它吵得很。2.2 jobs 输出里每一列都是什么我用一个实际例子来演示。打开一个终端执行sleep 300 然后执行jobs输出大概长这样[1] Running sleep 300 这一行怎么读[1]这是作业号从 1 开始编号每个 shell 会话里递增。它是你在当前会话里指代这个作业的“名字”。表示这是默认操作的作业。如果你直接敲fg不带参数恢复的就是这个带的作业。带-的是上一个默认作业当你把当前作业处理掉之后-会自动变成。Running当前状态。还可能是Stopped、Done、Terminated等。sleep 300 该作业对应的命令行。这里有个容易懵的点[1]是作业号不是进程 IDPID。作业号只在当前 shell 里有效换个终端就无效了PID 是全局的。两者别搞混。想同时看到 PID可以这么干jobs -l输出会多出一列 PID。这在排查“哪个进程在跑”的时候非常有用。3. 前后台切换的四板斧、CtrlZ、bg、fg工具很简单就四个但每个都有值得展开的细节。3.1 启动时直接放后台 与标准输入切断在命令末尾加shell 就会把这个命令放到后台启动同时立刻返回提示符。这个大家基本都会但有两个细节经常被忽略。第一个是标准输入。后台进程如果还试图从标准输入读取数据它会立刻收到 SIGTTIN 信号然后停止。因为后台进程不该读你的键盘否则就跟你前台输入冲突了。所以在写脚本的时候如果脚本里有read之类的交互操作你把它放后台跑它大概率会直接 Stopped 掉而不是报错。第二个是输出重定向。强烈建议后台任务的输出重定向到文件否则输出会继续占用你的终端。更麻烦的是如果你关掉终端输出写到已经关闭的终端设备上进程可能因为 I/O 错误而异常退出。所以一个“专业”的后台启动方式是这样的nohup python train.py train.log 21 这条命令里三个动作一次完成忽略挂断信号nohup、输出落盘 train.log 21、后台运行。我后面会专门讲 nohup。3.2 临时挂起CtrlZ 和 SIGTSTP你在前台跑一个命令跑着跑着突然想起还有个更急的事要先做。这时候不用慌按CtrlZ前台作业会立刻被暂停回到 Stopped 状态shell 给你提示符。CtrlZ发送的是 SIGTSTP 信号这个信号的默认行为是暂停进程。注意它和CtrlC发送 SIGINT默认终止完全不同。不少新手按完CtrlZ发现“命令没了”其实是进程被挂起了不是被杀了。举个例子tar -czf big.tar.gz /some/large/dir按下CtrlZ后shell 会显示[1] Stopped tar -czf big.tar.gz /some/large/dir这时候这个作业占用的 CPU 时间就没有了但内存里的状态全部保留。你可以去干别的然后再把它恢复。3.3 恢复与切换fg、bg 的参数规则fg %n把作业号 n 的作业调到前台运行。fg不带参数把标记的作业调到前台。bg %n把一个 Stopped 的作业放到后台继续运行。bg不带参数让标记的作业在后台运行。接上面的例子如果你不想等 tar 慢慢跑可以继续操作bg %1这样 tar 会在后台恢复运行。然后你可以继续在终端里敲别的命令。这里有个使用频率很高的组合操作我先跑个任务到前台等它跑一会后按CtrlZ暂停再用bg让它去后台跑最后用fg把它拿回前台。一套操作下来你会发现终端交互的掌控感完全不一样了。以前是“跑了就得等”现在是“随时可以打断、调度”。3.4 百分号后面跟什么作业标识符规则作业标识符除了%n按作业号之外常用的还有几种%名称按命令名开头匹配比如%tar会匹配tar -czf ...这个作业。%?名称按命令行中包含的子串匹配。%%等同于指当前默认作业。%、%-分别指默认作业和上一个默认作业。说实话日常用得最顺的还是fg不带参数和fg %1、fg %2这种按编号的。按名称匹配适合那种开了七八个后台作业、懒得数编号的场景fg %?backup直接用子串定位。4. 后台任务真正的大坑终端退出、SIGHUP 与记录的独立性这一章是整篇的精华也是绝大多数人在生产环境中翻车的地方。4.1 SIGHUP 信号的传播链路前面已经提过终端关闭时会向会话发送 SIGHUP。但你可能想不到SIGHUP 的传播路径在不同 shell 里还不一样。在传统的 shell 行为里当 shell 收到 SIGHUP 信号后会向当前会话里的所有作业包括后台运行的转发这个信号。也就是说你关终端shell 会把你的后台任务一并杀了而不是系统单独把任务杀了。在 bash 里这个行为由huponexit这个 shell 选项控制。你可以用shopt -s huponexit显式开启默认是关闭的。但这里有个差异bash 的非交互模式禁止设置这个选项。而且即使开了也只是说“退出时发送 SIGHUP 给作业”行为依然不乐观。所以最稳妥的理解是把后台任务和终端隔离别让终端退出这件事影响到它。4.2 disown让作业从作业表里“消失”disown是 bash 内置命令作用是把指定的作业从作业表中移除。移除之后shell 退出时就不会再管理这个作业了也就不会向它转发 SIGHUP 信号。用法sleep 300 disown %1这样即使你关闭终端sleep 也会继续跑。但注意disown 只解决了“shell 退出时转发信号”的问题它不管标准输入输出。如果这个进程还在往已关闭的终端上写输出依然可能出问题。所以规范操作仍然是输出重定向nohup python train.py train.log 21 disown实际上如果用了nohupdisown通常是多余的。但有时候你已经跑了个任务跑到一半发现可能要被终端关闭打断这时候再补一个disown就很救命。比如python train.py # 按 CtrlZ 暂停 bg disown %1这一套操作下来任务就在后台独立于终端运行了。4.3 nohup 与 setsid两条不同的“断线”路径nohup的原理是让进程忽略 SIGHUP 信号。因为进程主动忽略了信号所以无论是终端关闭还是 shell 退出发送过来的 SIGHUP 都被进程无视进程就继续活着。setsid更彻底它会创建一个新的会话让进程完全脱离当前终端。如果你setsid启动一个进程它会变成新会话的领头进程和原终端啥关系都没有了。两者对比工具机制适用场景nohup忽略 SIGHUP 信号只需要防“挂断”的场景最简单常用disown从 shell 作业表中移除任务已经启动之后想“断线”的场景setsid创建新会话完全脱离终端需要彻底隔离的场景比如启动守护进程日常服务器上最常用的组合就是nohup ... log 21 既不用写 systemd 服务开个终端就能把任务挂住简单直接。4.4 用 huponexit 做一个实验验证很多时候光看文档记不住不如做个实验。我在本地 bash 里验证过#!/bin/bash echo start: $$ sleep 60 echo end把这个脚本放后台跑关掉终端重新打开看进程是否还在。huponexit开和关各试一次你会很清楚信号传播的路径。我自己做过这个验证之后对 nohup 的必要性理解深了很多。5. 实战工作流从启动到收尾的一次完整作业调度理论说了一堆现在把它串成一个完整场景你在一台远程服务器上通过 SSH 登录需要执行一个耗时 20 分钟的数据处理任务但这个任务跑的时候你还想同时做别的分析。5.1 准备工作一个测试用的小脚本为了演示先写个简单脚本myjob.sh#!/bin/bash for i in $(seq 1 5); do echo task running: $i sleep 2 done echo task done给执行权限chmod x myjob.sh。5.2 三套常见组合操作场景 A启动时直接后台./myjob.sh job.log 21 用jobs确认它活着用cat job.log看输出。场景 B前台任务中途改后台./myjob.sh跑了一两秒后按CtrlZ显示 Stopped然后bg %1 jobs这时任务就在后台续跑了。场景 C需要任务在终端关闭后继续这个场景在服务器上最实用nohup ./myjob.sh job.log 21 然后你可以断开 SSH 去干别的任务照样跑。如果任务已经在前台跑了# CtrlZ bg disown5.3 脚本中等待后台任务wait 命令还有一种常见需求一个脚本里启动了好几个后台任务脚本本身需要等它们全部完成后再继续做别的事。这时候用wait。#!/bin/bash # 启动三个并行任务 python task_a.py python task_b.py python task_c.py # 等待所有后台任务完成 wait echo all task done如果你只关心其中某一个任务python task_a.py PA_PID$! python task_b.py PB_PID$! wait $PA_PID echo task_a finished$!是刚启动的后台进程的 PID在脚本里非常常用。这里有个细节wait不传参数时会等待当前 shell 的所有子进程。它返回的退出状态是所有被等待任务的退出状态中最后一个非零值如果存在。可以利用这个特性做简单的并行任务管理。6. 不同环境下的行为差异Git Bash、Windows 与“命令找不到”的坑6.1 Git Bash 里的 Job Control 和 Linux 不完全一样前面不少命令在 Linux 的 bash 里完全正常但如果你用的是 Windows 上的 Git Bash体验会有差异。Git Bash 本质上是跑在 Windows 上的一个模拟层它模拟了 POSIX 环境但底层的进程模型还是 Windows 的。所以表现上会有几个明显不同CtrlZ在许多 Windows 终端模拟器里行为不稳定。有时候按下去作业不会变成 Stopped反而会被挂成“假死”状态恢复也费劲。nohup在 Git Bash 里能用但配合 Windows 路径时输出重定向容易踩坑。比如/d/dev/project这种路径有时候需要写成D:\dev\project才能被外部程序识别。关闭 Git Bash 窗口时后台子进程的行为取决于终端模拟器的实现有时候进程会残留有时候会被强制杀干净并不像 Linux 里那样有统一的 SIGHUP 机制。所以我的建议是在 Windows 上做跨终端的长时间任务隔离别依赖 Git Bash 的 Job Control直接用start命令cmd或者 Windows 服务的方式更靠谱。Git Bash 里练习命令没问题但生产环境还是以 Linux 为准。6.2 screen: command not found 到底怎么处理这是个很经典的热搜词组合说明很多人都撞到过。你远程连了台服务器想跑个长时间任务听说screen能断线续跑一敲screen结果bash: screen: command not found这个原因通常很简单服务器上没装 GNU Screen。不是什么玄学问题。两种解决思路一是装一个sudo apt install screen # Debian/Ubuntu sudo yum install screen # CentOS/RHEL二是用我们前面讲过的 nohup 方案完全不依赖额外工具。我个人长期在只允许最小化安装的服务器上工作nohup ... 21 就是最可靠的方案。screen和tmux的优势在于可以随时重新附着会话查看输出但对于“只是跑一个长时间任务不看实时输出”的场景nohup 完全够用还少装一个东西。如果你确实想要 screen/tmux 的“断线重连”能力我推荐 tmux原因很简单它的窗口管理比 screen 舒服太多而且在主流发行版里都能一键装到。6.3 复制粘贴对作业控制的影响热搜词里有“git bash复制粘贴”我猜是有人发现在终端里按 CtrlZ 或 CtrlC结果只是执行了复制操作根本没发给 shell。这其实是 Windows Terminal、Git Bash 的默认配置问题。在 Windows Terminal 里CtrlC默认是复制除非你选中了文本否则才会发送中断信号。CtrlZ在有些终端里也被解释成了别的操作。处理办法很简单要么改终端的快捷键配置要么在按下这些组合键之后再敲一下回车确认。这个看似无厘头的问题在远程连服务器的时候特别容易让人崩溃。我在 Windows Terminal 里直接去设置里把CtrlC、CtrlZ的复制行为关掉或者绑定成仅“选中时复制”就再也没误触过。7. 作业控制与脚本编程的边界什么时候别用 Job Control讲完了用法和坑最后说一个原则性的问题Job Control 是给交互式终端用的不是给脚本编程用的。如果你在写脚本需要并行执行多个任务应该用wait、$!、甚至更高级的xargs -P、GNU parallel而不是试图在脚本里用fg、bg、%1这些操作。原因在于非交互式 shell 默认会关闭作业控制。在脚本里你无法像人一样“看一眼再决定”逻辑必须是确定的。脚本退出时对后台任务的管理依赖 SIGHUP 的传播路径容易有不确定性。举个例子你往脚本里写fg %1大概率会得到fg: no job control这样的报错。正确的做法是#!/bin/bash for url in $(cat urls.txt); do curl -s $url done wait echo All downloads completed.这种写法才是在脚本里做并行的正确打开方式。另外一个容易被忽略的问题是作业的生命周期和退出码。一个后台作业即使正常结束了它还留在作业表里直到你用jobs查看它一次它才会从表中被清掉。如果脚本里不 wait某些 shell 里这些“僵尸作业”会影响后续对作业表的判断。我在一个 CI 脚本里就遇到过这个问题脚本已经跑完了但后台子进程还在写文件导致后续步骤读数据不完整。加一个wait全部清爽。还有个小技巧如果你想让一个后台任务跑着但又不希望它的输出把日志文件撑爆可以用tail或head限制日志大小。不过这属于另一个话题了这里点到为止。8. 我的最后几条实战心得Job Control 这套东西在我的日常使用频率相当高几乎每次在服务器上跑长任务都会用到。总结几条不得不说的经验第一能重定向就重定向。任何后台任务都养成习惯加上 log 21不管有没有 nohup。这个习惯能避免九成“任务莫名其妙消失”的问题。第二CtrlZ 前想清楚。它在交互终端里是暂停但在某些终端模拟器和某些系统环境下可能因为快捷键冲突没有触发 SIGTSTP。按完之后看一眼 shell 是否有Stopped提示没有就赶紧换 CtrlC 止损。第三区分“挂了”和“死了”。任务没输出不一定是死了有可能是被 Stopped。看jobs输出里的状态比猜靠谱得多。排查远程进程时用ps -ef | grep 关键词辅助确认 PID 是否还在。第四生产环境的长时间任务优先考虑 tmux 或 systemd。Job Control 适合临时顶一下不适合长期服务。如果你要跑一个小时以上的东西建议用tmux开一个独立会话在里面随意跑断线了可以再附着回去比在普通终端里后台挂着要稳妥得多。第五别把 Job Control 当进程管理工具。它只能管理当前 shell 启动的、还没退出的作业。你要是用sudo edit切了身份或者开了子 shell作业表就不一定共享了。真要看全局进程ps、top、htop才是指定工具。我在实际使用中最舒服的状态是前台跑一个交互式命令临时切后台处理个紧急请求再切回来继续。这一套操作熟练之后你对系统资源的掌控感会有一种质变。像以前那种“一提交长任务就得盯着屏幕干等”的日子真的就一去不回了。
返回列表