CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Shell 与 Bash 入门/课时/进程与定时任务
RawBlame
课时目录12 节

Shell 与 Bash 入门

  1. 01Shell 是什么
  2. 02目录与文件导航
  3. 03文件操作与权限
  4. 04管道与文本处理
  5. 05变量与字符串
  6. 06条件与循环
  7. 07函数与脚本结构
  8. 08正则表达式
  9. 09进程与定时任务
  10. 10网络命令
  11. 11系统管理与编辑器
  12. 12综合练习

进程与定时任务

ps、jobs、cron、systemd timer。

35 分钟

进程与定时任务

这一课你将能做到

  1. 用 ps / top 定位 Java 进程,说清 kill -15 与 kill -9 的区别并正确重启应用
  2. 用 nohup / & / jobs / fg / bg 管理后台任务,SSH 断开服务不挂
  3. 编写并调试 crontab 定时任务,实现每天凌晨自动备份 MySQL

为什么要学

Spring Boot 应用在 Linux 上就是一个进程。发布要停旧起新、OOM 要找元凶、日志疯涨要定位写盘的进程——不懂进程管理,你只能对着告警干瞪眼。crontab 则是穷人的调度中心:没上分布式调度平台之前,数据库备份、日志清理、证书续期全靠它。面试里「kill -9 和 kill -15 的区别」「crontab 五个字段」「进程怎么优雅关闭」都是后端高频题,答不好会显得没碰过生产。

人话:进程就是「正在跑的程序」,每个都有身份证号(PID)。kill 不是「杀」而是「发信号」——-15 是敲门说「请收拾东西离开」(应用能执行 shutdown hook、释放连接池),-9 是直接抬走(Spring Boot 的优雅停机、MQTT 消息都来不及处理)。定时任务就是给系统请了个闹钟管家。

先建立图像

进程生命周期与信号路径:

java -jar app.jar
      │
      ▼
  内核分配 PID(如 12345)
      │
 kill -15 12345(SIGTERM,默认)        kill -9 12345(SIGKILL)
      │                                      │
 JVM 收到信号 → 执行 shutdown hook        内核直接回收,JVM 无感知
 优雅停机:停接新请求、处理完存量、        线程当场冻结,可能:
 关闭连接池、刷盘、退出                    - 数据没刷盘
                                          - 临时文件残留
                                          - 端口 TIME_WAIT 堆积

crontab 五字段口诀:

┌──────── 分  0-59
│ ┌────── 时  0-23
│ │ ┌──── 日  1-31
│ │ │ ┌── 月  1-12
│ │ │ │ ┌ 星期 0-7(0 和 7 都是周日)
│ │ │ │ │
* * * * *  要执行的命令
分 时 日 月 星期

例:0 3 * * *     每天 03:00
例:*/5 * * * *   每 5 分钟
例:0 9 * * 1-5   工作日每天 09:00

跟着做

步骤 1:看进程:ps 的两副面孔

ps -ef | grep java          # 风格 1:全格式列表
ps aux | grep mysql         # 风格 2:BSD 风格,含 CPU/内存占用
ps -ef | grep [j]ava        # 技巧:[j]ava 避免匹配到 grep 自己
pgrep -l java               # 只列进程名和 PID,最清爽
pstree -p | head -20        # 进程树,看父子关系

面试常问「ps -ef 和 ps aux 的区别」:内容重叠,-ef 强在完整命令行(看启动参数),aux 强在 %CPU %MEM 排序排查资源大户,ps aux --sort=-%mem | head 直接找内存 Top。

步骤 2:实时观察:top

top                  # 默认 3 秒刷新,q 退出
# 交互键:P 按 CPU 排序,M 按内存排序,1 展开每核
# 关键行解读:
# %Cpu(s): 12.3 us,  3.1 sy, 80.0 id,  4.0 wa
#   us=用户态  sy=内核态  id=空闲  wa=等磁盘(高 wa 先查 IO)
# load average: 0.5, 0.4, 0.3   ← 1/5/15 分钟负载,经验:不超过 CPU 核数

top 是进入服务器的第一眼体检,us 高=应用在算,wa 高=磁盘瓶颈,load 远超核数=过载。

步骤 3:优雅重启一个「应用」

用 sleep 模拟 Java 进程练手,不伤真服务:

mkdir -p ~/proc-lab && cd ~/proc-lab
nohup sleep 300 > sleep.out 2>&1 &     # 启动后台进程
echo $!                                # $! = 最近一个后台进程的 PID

pgrep -l sleep
kill <PID>                             # 默认就是 -15 优雅终止
pgrep -l sleep || echo "已退出"

nohup ... & 组合:& 放后台,nohup 让进程忽略挂断信号(SSH 断开也不死),输出自动写入 nohup.out 或重定向文件。

步骤 4:kill -15 vs kill -9 实测

# 先用 -15
nohup sleep 300 > /dev/null 2>&1 &
pid=$!
kill -15 $pid
sleep 1
pgrep -l sleep || echo "-15 后进程已退出"

# 模拟僵死进程用 -9(对 sleep 无感,真正的区别在 Java 应用)
nohup sleep 300 > /dev/null 2>&1 &
pid=$!
kill -9 $pid

生产脚本标准姿势:先 kill -15,等 30 秒轮询进程还在,才 kill -9 兜底。直接 -9 是新手最常被点名的坏习惯。

步骤 5:任务的前后台与 jobs

sleep 100                  # 前台运行,Ctrl+Z 暂停
# [1]+  已停止  sleep 100
jobs                       # 看任务列表
bg %1                      # 转后台继续跑
fg %1                      # 拉回前台,Ctrl+C 终止

这套操作适合「跑了半小时的命令突然想腾出终端」的场景;真正的长任务(如导数据)一开始就用 nohup。

步骤 6:crontab 自动备份 MySQL

mkdir -p ~/proc-lab/bin ~/proc-lab/backup ~/proc-lab/logs

cat > ~/proc-lab/bin/backup-mysql.sh << 'EOF'
#!/bin/bash
set -euo pipefail
LOG=/home/$USER/proc-lab/logs/backup.log
BK=/home/$USER/proc-lab/backup
echo "[$(date '+%F %T')] 备份开始" >> "$LOG"

mysqldump -uroot -p"你的密码" --single-transaction mydb \
  | gzip > "$BK/mydb-$(date +%F).sql.gz"

# 保留 7 天,删更早的
find "$BK" -name "mydb-*.sql.gz" -mtime +7 -delete
echo "[$(date '+%F %T')] 备份完成:$(ls -lh $BK | tail -1)" >> "$LOG"
EOF
chmod 700 ~/proc-lab/bin/backup-mysql.sh

# 测试脚本本身(没有 MySQL 可先注释 mysqldump 行验证流程)
bash ~/proc-lab/bin/backup-mysql.sh

# 编辑当前用户的 crontab
crontab -e
# 加入(每天 03:00,注意 % 在 crontab 里要转义或避免):
# 0 3 * * * /home/你的用户名/proc-lab/bin/backup-mysql.sh >> /home/你的用户名/proc-lab/logs/cron.log 2>&1

crontab -l                  # 查看已生效的任务
ls /etc/cron.d* /var/log/cron* 2>/dev/null   # 系统级配置与执行日志位置

--single-transaction 让 InnoDB 备份不锁表,是线上 mysqldump 的标配参数。

讲清楚要点

  1. kill 发的是信号,不是子弹:kill -15(SIGTERM)请求进程自行退出,进程可捕获并清理;kill -9(SIGKILL)内核强制回收,进程无法捕获也无法忽略。Java 应用停机永远先 -15。
  2. $! 与 $$ 别搞混:$! 是最近一个后台进程的 PID(写脚本常拿它记录或等待),$$ 是当前 Shell 自己的 PID。
  3. nohup 解决「SSH 断开进程就死」:SSH 会话结束时终端收到 SIGHUP,普通前台/后台进程随之退出;nohup 让进程忽略 SIGHUP。更正规的生产做法是 systemd 托管(systemctl restart myapp),第 11 课展开。
  4. crontab 有独立的环境:只有极简 PATH,不加载你的 .bashrc;相对路径、java 命令可能找不到。规范:脚本里全用绝对路径,环境变量写进脚本或 source /etc/profile;输出必须重定向到日志,否则邮件堆积。
  5. crontab 里 % 是特殊字符:会被解释成换行导致命令被截断。date +\%F 转义,或把复杂逻辑封装进脚本、crontab 只调脚本路径——后者是最佳实践。
  6. 僵尸进程与孤儿进程:子进程退出但父进程没回收 → 僵尸(Z 状态,占 PID 不占内存),父进程先死则孤儿被 init 收养。ps aux | grep ' Z ' 找僵尸,根因通常在父进程没 wait,重启父进程即可清理。

你可能会懵的地方

  • kill -9 为什么连 Java 的 shutdown hook 都不执行? SIGKILL 由内核直接处理,进程根本收不到信号,JVM 没机会跑任何钩子。所以 Spring Boot 的优雅停机(server.shutdown=graceful)、注册中心下线通知都依赖 -15 阶段完成。

  • crontab -e 保存后任务怎么被触发的? cron 守护进程(crond)每分钟醒来,比对 /var/spool/cron/用户名 里所有任务的时间字段,匹配则 fork 执行。所以 crontab 最小粒度是分钟;需要秒级用循环 + sleep 或专业调度器。

  • 怎么知道我的 crontab 到底跑没跑? 三查:crontab -l 确认配置在;grep CRON /var/log/syslog 或 /var/log/cron 确认 cron 有没有执行记录;看脚本自己的重定向日志。三层日志缺一不可。

  • ps -ef 里的 PPID 是什么? 父进程 PID。排查「谁启动了这个进程」时顺着 PPID 往上找,常见发现是「不是应用自己的脚本,是 supervisord/systemd 拉起来的」,这决定你 kill 之后它会不会被自动复活。

  • 后台任务退出终端后为什么有的死了有的活着? 没加 nohup 的后台任务在 Shell 退出时收到 SIGHUP 默认退出;nohup 或 setsid 启动的忽略它。Bash 有时可配 shopt -s huponexit 行为不同,别赌,长任务一律 nohup。

错法对照

错法问题正解
上来就 kill -9 java_pid跳过优雅停机,丢数据留尾巴先 kill -15,超时再 -9
crontab 直接写 java -jar ...cron 环境找不到 java写绝对路径或脚本内 export PATH
crontab 任务不重定向输出报错无处可看 + 邮件堆积>> 日志 2>&1
定时任务里用相对路径cron 工作目录是 $HOME,路径全错脚本内用绝对路径或 cd 固定
* * * * * 每 10 秒crontab 最小粒度 1 分钟循环 + sleep 封装,或换调度工具

卡点急救

  1. crontab 不执行:按顺序查——crontab -l 确认任务在 → /var/log/cron 确认触发过 → 手动用 sh 脚本 跑确认脚本本身没问题 → 检查脚本内是否依赖了交互环境的 PATH/变量 → 确认输出重定向了能看到报错。
  2. kill -15 后进程不退出:应用可能在等长连接收尾。等 30 秒(可用 for 循环 + pgrep 轮询)再 -9;频繁卡住则排查应用优雅停机配置和线程池是否设置了 daemon。
  3. nohup: ignoring input and appending output to nohup.out 看不到输出:这是提示不是报错,输出在 nohup.out。规范做法启动时就重定向:nohup cmd > app.out 2>&1 &。

面试常问

  • kill -15 和 kill -9 的区别? -15 发送 SIGTERM,可被进程捕获,JVM 会执行 shutdown hook:停止接收新请求、处理完存量、关闭连接池后退出,实现优雅停机;-9 发送 SIGKILL,内核直接回收进程,不可捕获不可忽略,可能造成数据未落盘、注册中心残留节点。生产标准流程:-15 → 轮询等待 → 超时 -9 兜底。

  • crontab 五个字段的含义?写一个每天凌晨 3 点备份 MySQL 的任务。 分 时 日 月 星期。答案:0 3 * * * /opt/scripts/backup-mysql.sh >> /var/log/backup.log 2>&1。加分点:说明备份脚本内含 --single-transaction 防锁表、find -mtime +7 -delete 清理旧备份、输出重定向便于排查、cron 环境变量有限所以脚本内用绝对路径。

  • CPU 飙高怎么排查? ① top 找到高 CPU 的 Java 进程 PID;② top -Hp PID 或 ps -T -p PID 找到具体线程;③ printf '%x' 线程号 转十六进制;④ jstack PID | grep 十六进制 -A 20 定位线程栈。前两步全是 Shell 命令——这就是进程管理知识直接对接 JVM 排查的例子。

自测三问

  1. 为什么停 Spring Boot 应用不能直接 kill -9?
  2. 写出「工作日每天 09:00 执行 xx.sh」的 crontab 行。
  3. nohup cmd & 中两个部分各自解决什么问题?

小结一句话

进程管理是「发对信号」:ps/top 看清楚、-15 优雅停、-9 只兜底、nohup 保后台;crontab 是「请对管家」:五个字段 + 绝对路径 + 日志重定向,MySQL 凌晨自动备份就这么简单。

延伸

  • crontab.guru 在线表达式校验
  • 下一课:网络命令——curl 测接口、ss 查端口,把服务「通不通」一眼看穿。
加载本节练习…
← 上一课正则表达式下一课 →网络命令