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综合练习

综合练习

编写备份脚本 + cron 定时。

40 分钟

综合练习

这一课你将能做到

  1. 独立写出「MySQL 定时备份脚本」:打包、时间戳命名、保留 7 天、日志记录、crontab 调度全流程
  2. 独立写出「多节点巡检脚本」:循环远程主机,采集磁盘/内存/负载/进程状态并汇总成报告
  3. 独立写出「日志分析流水线」:从 Nginx 访问日志统计 Top IP、错误率,从应用日志统计异常数

为什么要学

前面 11 课你学了单词,这一课开始写文章。真实工作里没有「用一下 grep」这种需求,只有「给我做一个数据库每天自动备份」「发版前帮我巡检一遍所有节点」这种完整任务。把它们拆解成前面学过的命令组合,正是 Shell 工程师的日常。面试手写题也几乎都出在这个粒度:「写个备份脚本」「写个巡检脚本」——本课三个脚本就是你的答案模板,背熟结构、理解每行,面试直接默写。

人话:这就像 Java 里学完语法开始写第一个完整的 CRUD 项目。三个脚本对应三类最高频运维任务:备份(保数据)、巡检(保稳定)、分析(找问题),它们会一直陪着你到每一份后端工作里。

先建立图像

三个脚本的技能映射图:

┌───────────────┬──────────────────────────────────────┐
│ 脚本          │ 用到的课程技能                        │
├───────────────┼──────────────────────────────────────┤
│ 备份脚本      │ 变量(5) + 条件(6) + 函数(7) + tar(3)  │
│               │ + find 清理(2) + crontab(9)          │
├───────────────┼──────────────────────────────────────┤
│ 巡检脚本      │ 函数(7) + 循环(6) + ssh/scp(10)      │
│               │ + df/free/uptime(11) + awk(4)        │
├───────────────┼──────────────────────────────────────┤
│ 日志分析      │ 管道(4) + 正则(8) + sort/uniq(4)     │
│               │ + awk/sed(4) + grep -E(8)            │
└───────────────┴──────────────────────────────────────┘

生产脚本的五要素(每个脚本都必须有):

1. 安全带   set -euo pipefail
2. 可配置   变量集中在文件头部
3. 防御     目录/参数检查,失败即退出
4. 留痕     每步写日志,时间戳开头
5. 自清理   旧产物定期删除,不无限膨胀

跟着做

练习 1:MySQL 定时备份脚本(完整可运行)

mkdir -p ~/practice/bin ~/practice/backup ~/practice/logs
cat > ~/practice/bin/backup-mysql.sh << 'EOF'
#!/bin/bash
set -euo pipefail

# ========== 可配置区 ==========
DB_USER="root"
DB_PASS="your_password"            # 生产建议用 ~/.my.cnf 免密方式
DB_NAME="mydb"
BACKUP_DIR="$HOME/practice/backup"
KEEP_DAYS=7
# ==============================

log() { echo "[$(date '+%F %T')] $*" >> "$HOME/practice/logs/backup.log"; }
die() { log "ERROR: $*"; exit 1; }

mkdir -p "$BACKUP_DIR"
command -v mysqldump > /dev/null || die "mysqldump 不存在,请安装 mysql-client"

FILE="$BACKUP_DIR/${DB_NAME}-$(date +%F-%H%M).sql.gz"
log "开始备份 $DB_NAME -> $FILE"

# --single-transaction: InnoDB 不锁表;管道失败整体失败(pipefail)
mysqldump -u"$DB_USER" -p"$DB_PASS" --single-transaction "$DB_NAME" \
  | gzip > "$FILE"

[[ -s "$FILE" ]] || die "备份文件为空:$FILE"     # -s 判断文件存在且非空
log "备份完成,大小:$(ls -lh "$FILE" | awk '{print $5}')"

# 清理 N 天前的旧备份
find "$BACKUP_DIR" -name "${DB_NAME}-*.sql.gz" -mtime +$KEEP_DAYS -delete
log "已清理 ${KEEP_DAYS} 天前的旧备份"
log "===== 本次任务结束 ====="
EOF
chmod 700 ~/practice/bin/backup-mysql.sh

# 没装 MySQL 也先跑一次,观察 die() 如何拦截并记录日志
bash ~/practice/bin/backup-mysql.sh; echo "退出码:$?"
tail -5 ~/practice/logs/backup.log

装入 MySQL 的机器上去掉注释实测;定时调度(crontab -e):

# 每天 03:00 执行,输出并入日志
0 3 * * * /home/你的用户名/practice/bin/backup-mysql.sh >> /home/你的用户名/practice/logs/cron.log 2>&1

验证技巧:先写 下一分钟 的时间(date +%M 加一),观察 cron.log 是否准时出现记录,再改回 0 3 * * *。

练习 2:多节点巡检脚本

cat > ~/practice/bin/inspect.sh << 'EOF'
#!/bin/bash
set -uo pipefail

# 巡检目标:本机演示。有多台机器时改为 IP 列表 + ssh 远程执行
REPORT="$HOME/practice/logs/inspect-$(date +%F).txt"

get_disk()  { df -h / | awk 'NR==2 {print $5}'; }        # 根分区使用率
get_mem()   { free | awk '/Mem:/ {printf "%.0f%%", $3/$2*100}'; }
get_load()  { uptime | awk -F'load average:' '{print $2}'; }
check_app() { pgrep -f "java.*myapp" > /dev/null && echo "RUNNING" || echo "DOWN"; }

main() {
  {
    echo "===== 巡检报告 $(date '+%F %T') ====="
    echo "磁盘使用率 : $(get_disk)   (阈值 80%)"
    echo "内存使用率 : $(get_mem)     (阈值 85%)"
    echo "负载       :$(get_load)"
    echo "myapp 进程 : $(check_app)"
    echo "最近登录   : $(who | head -3)"
  } >> "$REPORT"
  echo "报告已生成:$REPORT"
}
main
EOF
bash ~/practice/bin/inspect.sh
cat ~/practice/logs/inspect-*.txt

多机版只需两处升级:节点列表 NODES=("10.0.0.5" "10.0.0.6"),取数改走远程 ssh -o ConnectTimeout=3 deploy@$node 'df -h /' (前提:第 10 课的 ssh 免密已配好)。把 get_disk 里的命令换成 ssh ... "df -h /" 即可,函数结构完全不变——这就是函数化的价值。

练习 3:日志分析流水线

# 造一份仿真 Nginx 日志
cat > ~/practice/access.log << 'EOF'
10.0.0.1 - - [06/Oct/2026:10:00:01] "GET /api/user HTTP/1.1" 200 1024
10.0.0.2 - - [06/Oct/2026:10:00:02] "GET /api/user HTTP/1.1" 502 512
10.0.0.1 - - [06/Oct/2026:10:00:03] "POST /api/order HTTP/1.1" 500 2048
10.0.0.3 - - [06/Oct/2026:10:00:04] "GET /api/user HTTP/1.1" 200 512
10.0.0.2 - - [06/Oct/2026:10:00:05] "GET /api/order HTTP/1.1" 502 512
10.0.0.1 - - [06/Oct/2026:10:00:06] "GET /api/user HTTP/1.1" 404 256
10.0.0.4 - - [06/Oct/2026:10:00:07] "GET /api/user HTTP/1.1" 200 512
EOF

LOG=~/practice/access.log
echo "== 总请求数:$(wc -l < $LOG)"

echo "== 状态码分布 =="
awk '{print $9}' $LOG | sort | uniq -c | sort -rn

echo "== 5xx 错误率 =="
total=$(wc -l < $LOG); err=$(awk '$9 >= 500' $LOG | wc -l)
echo "$err / $total = $(awk "BEGIN{printf \"%.1f%%\", $err/$total*100}")"

echo "== 请求量 Top3 IP =="
awk '{print $1}' $LOG | sort | uniq -c | sort -rn | head -3

echo "== 502 都来自谁 =="
awk '$9 == 502 {print $1}' $LOG | sort -u

echo "== 应用日志 ERROR 计数(有真实日志时)=="
grep -c "ERROR" ~/net-lab/server.log 2>/dev/null || echo "无日志或无 ERROR"

每一条都能拆开单独跑——这正是第 4 课教的「逐段验证法」。把这段存成 log-analyze.sh,参数化 LOG=${1:?用法:$0 <日志路径>},就是一个可复用的分析工具。

练习 4:三脚本统一 crontab

crontab -e
# 巡检:每 30 分钟一次,报告自动按天落盘
*/30 * * * * /home/你的用户名/practice/bin/inspect.sh >> /home/你的用户名/practice/logs/cron.log 2>&1

至此你拥有一套「自动备份 + 定时巡检 + 随手分析」的迷你运维平台——全是用前 11 课的零件手工组装的。

讲清楚要点

  1. 备份脚本的核心三防线:--single-transaction 不锁表、[[ -s $FILE ]] 验证产物非空、find -mtime +N -delete 防磁盘慢性死亡。缺一条都算不上合格备份。真正可靠的方案还要异机/异地存储,脚本内 rsync 推到备份机是进阶方向。
  2. 巡检脚本要「输出结构化」:每行一个指标、带阈值标注,人看省脑、机器(脚本/告警系统解析)也省力。把超阈值项额外 echo 到 stderr,接告警通道就是半自动化监控。
  3. 日志分析永远是「先过滤再统计」:先 grep/awk 缩小数据面,再 sort/uniq 聚合。直接对原始日志 uniq 是新手常见低效做法;百万行日志先按日期截断(grep "^2026-10-06")能把处理时间降一个量级。
  4. 所有脚本共享同一个日志规范:[时间] 消息 追加写入、报错走 die() 统一格式。排查定时任务时,规范日志能让你 30 秒定位问题,散装 echo 则要翻半天。
  5. 调度验证用「下一分钟法」:先用 date 算出下一分钟写入 crontab,确认触发和产物都正常,再改回正式时间。直接上正式表达式然后等第二天发现没跑,是最浪费时间的做法。
  6. 脚本要能被别人看懂:可配置区在头部、函数有名字、关键步骤有日志。你写的运维脚本大概率会被同事接手,可读性就是可靠性的一部分。

你可能会懵的地方

  • 密码明文写在脚本里安全吗? 不安全,chmod 700 只是防同机其他用户,看不到密钥泄漏风险。生产做法:MySQL 用 ~/.my.cnf(600 权限)存凭据实现免密,或接入密钥管理。至少做到「密码不进 Git 仓库」。

  • 巡检脚本里 ssh 失败会让整个循环中断吗? 不加 set -e 会继续(所以练习里只用 -uo pipefail);更稳的做法是每次 ssh 加 -o ConnectTimeout=3 和 || echo "$node 不可达",把失败记录为数据而不是让脚本崩溃——巡检脚本的第一原则是「自己别挂」。

  • 错误率统计里的 awk "BEGIN{...}" 是什么? awk 的 BEGIN 块在读输入前执行,常用于纯计算(不需要输入数据)。这里借它做浮点除法——bash 原生 $(( )) 只支持整数,遇到百分比这种小数就交给 awk 或 bc。

  • 备份文件的命名为什么带 %H%M(时分)? 一天内可能多次备份(每次发版前备份是常见策略),同名会互相覆盖。文件名带越精确的时间戳,恢复时选择余地越大。命名即元数据,这是运维习惯里性价比最高的一条。

  • 这些脚本可以直接用于生产吗? 结构和思路可以直接迁移,但生产化还差几件事:凭据管理、异机备份、失败告警(接企业微信/钉钉 webhook)、以及最重要的——先用测试库验证再上生产。本课目标是「结构和每行都懂」,生产化是水到渠成的下一步。

错法对照

错法问题正解
备份后不验证产物空文件也当成功,出事才知道`[[ -s $FILE ]]
crontab 里直接写多行逻辑% 转义、PATH、引号全是坑封装脚本,crontab 只调路径
旧备份不清理磁盘慢性死亡,最终把系统盘写满find -mtime +N -delete
巡检脚本 set -e 全开一台机器 ssh 失败,全场中断每次检查独立容错,失败记为数据
日志分析一次管道从头堆到尾中间出错无法定位逐段验证,确认后再串全

卡点急救

  1. crontab 到点没执行:按第 9 课清单逐层查——配置在不在(crontab -l)、cron 服务活着吗(systemctl status cron)、执行日志有没有记录(/var/log/cron)、脚本手动跑有没有报错、输出重定向了吗。绝大多数答案在「cron 环境和交互环境不同」里。
  2. 备份出来文件是空的:mysqldump 阶段失败但管道吞了错误。检查 pipefail 是否开启、手动跑 mysqldump 看具体报错(密码错、库不存在、磁盘满最常见)。
  3. 巡检报告重复内容堆积:脚本被多次调度写入同一文件。报告按天命名(inspect-$(date +%F).txt)并在头部加时间戳,或调度前先判断是否已有当次运行标记。

面试常问

  • 现场写一个 Shell 脚本:每天凌晨备份 MySQL 并保留 7 天。 答题骨架:变量区(库名、目录、保留天数)→ log/die 函数 → mysqldump --single-transaction | gzip → [[ -s ]] 验证 → find -mtime +7 -delete → crontab 0 3 * * * 脚本路径 >> 日志 2>&1。说出「不锁表、验证产物、自动清理」三个关键词就是高分答案。

  • 你有写过的自动化运维脚本吗?讲讲设计思路。 参考思路:三个脚本(备份/巡检/日志分析)共同遵循「set -euo pipefail + 配置头部集中 + 函数封装 + 统一日志 + 失败即退出」五要素;巡检脚本预留 ssh 远程执行接口实现多机扩展;所有脚本可被 crontab 调度且输出重定向留痕。重点展示「结构化思维」而不是罗列命令。

  • 如何统计昨天 Nginx 日志中每个接口的平均响应时间 Top5? 前提:日志里有 $request_time 字段。思路:awk '$4 ~ "06/Oct/2026" {split($7,a,"?"); sum[a[1]]+=行内耗时; cnt[a[1]]++} END {for (u in sum) printf "%.3f %d %s\n", sum[u]/cnt[u], cnt[u], u}' access.log | sort -rn | head -5。考点:awk 关联数组、split 去掉查询参数、END 块汇总输出、sort -rn 排序。

自测三问

  1. 备份脚本里 [[ -s "$FILE" ]] 防的是什么事故?
  2. 巡检脚本为什么用 set -uo pipefail 而不开 -e?
  3. 把本机巡检改成多机巡检,最少需要改哪几处?

小结一句话

备份保数据、巡检保稳定、分析找问题——三个脚本五要素(安全带、可配置、防御、留痕、自清理)跑通,你就完成了从「会敲命令」到「会写工具」的跨越。

延伸

  • ShellCheck:脚本静态检查工具
  • 下一步建议:把本课脚本用 ShellCheck 扫一遍并修正告警;进阶学习 systemd 定时器与 Ansible 批量运维,向基础设施自动化方向深入。
加载本节练习…
← 上一课系统管理与编辑器