Shell 后台任务管理

在终端里起了个开发服务器,比如 npm run dev,突然想装个包或者查个日志,结果窗口被霸占了。多数人的做法是 Ctrl+C 杀掉,开个新标签页重来。其实大可不必。

Shell 自带一套后台任务管理机制,用好了,不用杀进程也不用开新窗口。

先说三个基础命令

在命令末尾加 &,Shell 会把这个任务丢到后台,立刻把提示符还给你:

hermes dashboard &

回车后返回 [1] 23456[1] 是作业号,23456 是 PID。这时候你可以继续在同一窗口敲别的命令。

jobs 列出当前终端后台挂着的所有任务:

 jobs
[1]  + running    hermes dashboard
[2]  - running    python heavy_script.py

fg %1 把 1 号作业拉回前台,bg %1 让暂停的作业在后台继续跑。就这么简单。

Ctrl+Z 的坑

很多人以为 Ctrl+Z 等于后台运行。不是。

Ctrl+Z 发的是 SIGTSTP 信号,本质是暂停,不是后台。进程进入 Stopped 状态后,代码完全不执行了,CPU 占用归零。它不会在后台悄悄干活,它只是被冻住了。

如果这时候你直接关掉终端窗口,Shell 会给所有子进程发 SIGHUP。一个冻住的进程收到 SIGHUP,就直接被杀掉了,没有余地。

忘记加 & 怎么办

你起了个大编译任务,跑了 10 分钟才发现忘加 &。别按 Ctrl+C,三步搞定:

  1. Ctrl+Z,冻住进程,拿回提示符。
  2. 输入 bg,进程在后台恢复运行。
  3. 输入 disown,让进程脱离当前终端。

disown 是关键。执行后,即使你关掉终端,进程也会被 init 或 systemd 接管,安安稳稳把活干完。

disown 之后怎么管

一旦执行了 disown,这个进程就从当前 Shell 的家谱里被除名了。它不再是一个作业(Job),所以 jobs、fg、bg 对它全部失效。

但它没有失控。它现在是一个普通的系统级进程,你需要切到底层视角,用管进程的方式去调教它。

找到它:拿到 PID

disown 之后你手里没有作业号了,只有 PID。如果启动时没记住那串数字,用 pgrep 通过名字查:

pgrep -a hermes

-a 参数会把进程启动时的完整命令也打印出来,防止你认错人。传统流派用 ps aux | grep hermes,效果一样,多打几个字而已。

杀掉它:发信号

拿到 PID 后,想让它停下来不能靠 Ctrl+C 了,得用 kill 发信号:

kill 23456       # SIGTERM,允许进程保存数据后体面退出
kill -9 23456    # SIGKILL,不给反应时间,瞬间断电

懒得查 PID 的话,pkill hermes 直接按名字杀,前提是系统里只有一个同名的进程在跑。

看它吃多少资源

想确认这个脱管的进程是不是在后台疯狂吃 CPU 和内存,打开 htop 或 btop,按 F4 或 / 过滤进程名,就能实时盯着。

看它的实时输出

这是 disown 最让人抓狂的地方。如果当初没做日志重定向,进程的标准输出要么还往你终端屏幕上刷,要么随终端关闭彻底看不到了。

Linux 一切皆文件,进程的输出在 /proc 目录下有记录。查到 PID 后(假设是 23456):

sudo tail -f /proc/23456/fd/1

fd/1 就是标准输出的文件描述符。这招能临时看一眼,但不够稳定,进程重启后 PID 就变了。

操作对照表

操作内核信号进程状态关闭终端后
command &Running死(收到 SIGHUP)
Ctrl+ZSIGTSTPStopped
bgSIGCONTRunning死(仍绑定终端)
command & + disownRunning
Ctrl+CSIGINTTerminated已经死了

长期服务别用这套

&disown 适合临时救急。如果你跑的是需要长期常驻的服务,比如 NAS 上的爬虫或者自建的 API 网关,老老实实用 systemd 或者 tmux。Shell 快捷键管的是临时任务,守护进程管的是长期服务。

如果一个任务你频繁需要查日志、频繁需要手动暂停恢复,也别用 & + disown。开个 tmux 虚拟窗口,随时分离随时重连,体验好得多。