主题
界面
主题
前面学的命令都是"一次一条"。脚本把它们串起来,变成"可重复执行的一件事" ✓
命令: tail -f app.log (每次都要手敲)
脚本: ./deploy.sh start (把 20 个步骤固化成一个动作)★脚本的价值不只是"省事":
| 价值 | 说明 |
|---|---|
| 可重复 ★ | 每次执行都一样,不会因为"这次忘了第 7 步"而出错 ✓ |
| 可版本化 | 脚本进 Git,团队共享、可评审 ✓ |
| 可自动化 | 能被 CI / 定时任务调用 ✓ |
完成本节后,你应该能够:
#!/usr/bin/env bash);$1、$#、$@)与默认值(${1:-default})★;if 判断 ★★★;for / while 循环与函数;set -euo pipefail 为什么该成为默认开头 ★★★;bash -n / bash -x / shellcheck 调试;先看一个"手工操作"的典型事故:
发布步骤(文档里写着):
1. 备份 2. 停服务 3. 替换 jar 4. 启动 5. 检查
某次执行时,第 5 步忘了 → 线上跑着一个启动失败的版本 ✗
如果写成脚本:第 5 步不可能被跳过 ✓脚本把"应该做什么"从"文档里的文字"变成"可执行的事实" ✓
这与 《Git Flow 与团队协作》 的"分支保护"、《代码规范与自动校验》 的"规范检查"是同一条原则: 把约定变成机制 ✓
#!/usr/bin/env bash # ★ shebang:告诉系统用哪个解释器两种执行方式:
| 方式 | 命令 | 需要执行权限吗 |
|---|---|---|
| 作为解释器参数 | bash script.sh | ❌ 不需要 ✓ |
| 直接执行 | ./script.sh | ✅ 需要(chmod +x)★ |
⚠️ 新手最常问的"Permission denied":
./deploy.sh # ✗ Permission denied
chmod +x deploy.sh # ✓ 加执行位([S3-033](./S3-033))
./deploy.sh赋值(等号两边不能有空格 ⚠️):
name="Alice" # ✓
name = "Alice" # ✗ 会被当成命令 name使用:
echo "$name"
echo "${name}" # ★ 推荐:带花括号,边界明确★ 引号的三条规则(这是本节最容易错的地方):
| 引号 | 变量会展开吗 | 用在 |
|---|---|---|
双引号 " " ★ | ✅ 会 | 默认用这个 ✓ |
单引号 ' ' | ❌ 不会 | 原样输出(如正则里的 $)✓ |
| 无引号 | ✅ 会(但会做分词 ✗) | 不要用(除非你明确想要分词)⚠️ |
无引号的危险(真实 bug 来源):
file="my report.txt"
rm $file # ✗ 被分词成 rm "my" "report.txt" → 删了两个文件 ✗✗
rm "$file" # ✓★ 规则:引用变量时,几乎总是加双引号 ✓
命令替换(把命令的输出赋给变量):
today=$(date '+%F') # ★ 推荐 $( )
count=$(grep -c ERROR app.log)
# 反引号 `date` 也行,但不推荐(嵌套困难、易混淆)./deploy.sh prod v1.2.0| 变量 | 含义 |
|---|---|
$0 | 脚本自己 |
$1 $2 | 第 1、2 个参数 ✓ |
$# | 参数个数 ★ |
$@ | 所有参数(保持各自独立)★ |
$* | 所有参数(合并成一个字符串,⚠️ 一般不用) |
$? | 上一个命令的退出码 ★★ |
默认值与必填校验 ★:
ENV="${1:-dev}" # 没传就用 dev ✓
JAR="${2:?用法:$0 <环境> <jar 路径>}" # ★ 没传就报错并退出 ✓${var:?msg} 非常实用 ——它让脚本"参数不对就拒绝执行",而不是带着空值跑下去 ✗
这是脚本的"控制流基础" ✓
if grep -q "ERROR" app.log; then
echo "有错误"
else
echo "没有错误"
fi★ if 判断的就是"命令的退出码"(0 = 真,非 0 = 假)✓
不需要 [ ] 的常见写法:
if command; then ... fi # ✓ 直接判断命令
if [ -f "$file" ]; then ... fi # 判断文件存在
if [ -z "$var" ]; then ... fi # 判断字符串为空
if [ "$a" = "$b" ]; then ... fi # 字符串相等(注意空格 ★)⚠️ [ ] 里的空格不能省:
if [$a = $b]; then # ✗
if [ "$a" = "$b" ]; then # ✓文件测试(部署脚本常用)★:
| 表达式 | 含义 |
|---|---|
-f | 是普通文件 ✓ |
-d | 是目录 ✓ |
-e | 存在(任何类型)✓ |
-s | 存在且非空 ★(判断"打包成功"时用)✓ |
-r / -w / -x | 可读 / 可写 / 可执行 ✓ |
主动设置退出码 ★:
exit 0 # 成功 ✓
exit 1 # 失败 ✗★ 这就是脚本能被 CI / 监控判断成败的原因(《代码规范与自动校验》)✓
# for
for file in *.log; do
echo "处理 $file"
done
# 计数循环
for i in $(seq 1 10); do sleep 1; done # ★ 等待/重试时常用
# while(常用于"最多等 N 次"的重试)★
attempt=0
while [ $attempt -lt 10 ]; do
curl -fsS "$URL" && break
attempt=$((attempt + 1))
sleep 3
done函数(让脚本可读):
log() { echo "[$(date '+%F %T')] $*"; } # ★ 带时间戳的日志函数
start() {
log "启动..."
# ...
}
start # 调用(不加括号 ✓)★ 一个习惯:每个脚本都定义一个 log() ——带时间戳的输出对事后复盘极有价值 ✓
set -euo pipefail ★★★ 这是本节最该记住的一行 ✓
#!/usr/bin/env bash
set -euo pipefail三个选项分别做什么:
| 选项 | 作用 | 为什么重要 |
|---|---|---|
-e | 任何命令失败就退出 ★ | 避免"第 3 步失败了,脚本还继续跑第 4、5 步" ✗✗ |
-u | 用了未定义变量就报错 ★ | 避免 rm -rf $DIR/* 在 DIR 为空时变成 rm -rf /* ✗✗✗ |
-o pipefail | 管道中任一环节失败,整体算失败 ★ | 默认只看最后一个命令的退出码 ✗ |
★ -o pipefail 的例子:
grep ERROR app.log | wc -l
# 如果 grep 失败(文件不存在),默认整体退出码 = wc 的(0) → "看起来成功" ✗
# 加了 pipefail → 整体非 0 ✓⚠️ 什么时候"不用" -e:
当你想"收集所有问题再一起报告"时(如本节的
check-project.sh)——那时你会自己检查每个命令的结果 ✓但即使这样,
-u和pipefail通常仍要保留 ✓
| 方法 | 作用 |
|---|---|
bash -n script.sh ★ | 只检查语法(不执行)——改完脚本先跑这个 ✓ |
bash -x script.sh ★★ | 打印每一条执行的命令(逐行跟踪)——排查"为什么这样"的神器 ✓ |
shellcheck script.sh ★★★ | 静态检查(能发现引号、变量、语法等各类问题)——强烈推荐安装 ✓ |
set -x / set +x | 在脚本里临时开关跟踪 ✓ |
★ 本节的脚本就自带了"语法自检"(bash -n "$0")✓ ——这是一个值得模仿的习惯 ✓
shellcheck 能发现什么(举例):
| 问题 | 提示 |
|---|---|
| 变量没加引号 | SC2086: Double quote to prevent globbing ★ |
$? 用法不对 | SC2181 |
用了 [ 而不是 [[ | SC2292(bash 里推荐 [[) |
三个让脚本从"能跑"变成"可交付"的习惯 ★:
| 习惯 | 做法 | 为什么 |
|---|---|---|
| ① 参数校验 | ${1:?用法...} + [ -f "$file" ] | 错的参数要在第一步就拒绝 ✓ |
| ② 日志 | log() 带时间戳 + 关键步骤都输出 | 事后能复盘 ✓ |
| ③ 幂等 ★ | 重复执行结果一致(mkdir -p、先判断是否已运行) | 失败后重跑是常态 ✓ |
幂等为什么重要:
脚本执行到一半失败了,你一定会再跑一次 ✓ ——如果脚本不幂等,第二次可能造成新的破坏 ✗
例子:
mkdir dir第二次会报错 ✗ → 用mkdir -p✓
#!/usr/bin/env bash
set -euo pipefail
name="${1:-world}"
echo "hello, $name"见第 7 节(check-project.sh 的完整实测)✓
set -e 的部署脚本 cd /opt/app # 如果目录不存在,失败
cp new.jar app.jar # ✗ 但脚本继续跑:把 new.jar 拷到了"当前目录"
systemctl restart app # ✗ 重启了一个没更新的版本
echo "发布完成 ✓" # ✗ 报告成功 ✗✗★ 这正是 set -e 要防的:一步失败,后续全部在错误的前提上执行,最后还报告成功 ✗✗
文件:labs/engineering/check-project.sh
运行(Windows 用 Git Bash):
& "C:\Program Files\Git\bin\bash.exe" -c 'cd <仓库根目录> && bash labs/engineering/check-project.sh .'预期输出(真实运行结果)★:
===== 1. 基本信息 =====
目录 : <仓库根目录>
主机名 : dev-host
当前用户 : testuser
时间 : 2026-09-21 16:34:50
===== 2. 文件统计 =====
Markdown 文件 : 93
Java 文件 : 135
===== 3. 检查大文件(> 500KB 的文档)=====
docs/.vitepress/cache/deps/chunk-C2KGWKLW.js.map
[提示] 以上文件偏大,考虑拆分
===== 4. 检查技术债标记 =====
含 TODO/FIXME 的文件数 : 9
===== 5. 检查构建产物是否被误提交 =====
构建产物未被提交 ✓
===== 6. 检查脚本自身的语法 =====
语法检查通过 ✓
===== 结果 =====
全部通过 ✓
脚本退出码=0脚本里用到的技术点对照 ★:
| 位置 | 技术点 | 本节章节 |
|---|---|---|
set -uo pipefail | 安全选项(未用 -e,因为要收集所有问题) | 5.6 ★ |
ROOT="${1:-$(pwd)}" | 默认值 + 命令替换 | 5.2 / 5.3 |
problems=0 / problems=$((problems + 1)) | 变量与算术 | 5.2 |
section() { ... } | 函数 | 5.5 |
if [ -n "$large" ] | 条件判断 | 5.4 |
bash -n "$0" | 语法自检 ★ | 5.7 |
exit 0 / exit 1 | 退出码 ★ | 5.4 |
★ 一个值得注意的设计:这个脚本"刻意不用 set -e" ——因为它要检查 6 项并报告所有问题,而不是第 1 项失败就停 ✓
这说明:规则是工具,不是教条 ——知道为什么用,才知道什么时候不用 ✓
cd "$ROOT" 改成 cd $ROOT,传一个带空格的路径 —— 观察报错 ✗(体会引号的价值)✓;-u:在脚本里引用一个没定义的变量 —— 观察它报错退出 ✓(对比不加 -u 时"静默用空值")✗;pipefail:grep x 不存在的文件 | wc -l; echo $? —— 加与不加 pipefail 的对比 ✓;bash -x:用它跑一遍 check-project.sh —— 观察每一行实际执行的样子(变量被展开后的真实命令)★;fi —— 用 bash -n 观察它报错 ✓。见 5.2。文件名带空格时会导致灾难 ✗
name = "x" # ✗ 被当成命令
name="x" # ✓set -e,一步失败后还继续 见 6.3。最危险的一类 ✗✗
$? 被"覆盖" command
if [ $? -eq 0 ]; then ... # ⚠️ 能工作但啰嗦
# ★ 更简洁:
if command; then ... # ✓ 直接判断更隐蔽的坑:
command
echo "执行完了" # ✗ echo 覆盖了 $?
if [ $? -eq 0 ]; then ... # 这里判断的是 echo 的退出码 ✗✗修:先把 $? 存到变量,或直接用 if command ✓
./x.sh 见 5.1。chmod +x ✓
cd /opt/app
cat config/app.yml # 如果 cd 失败,就在"当前目录"找 ✗修:用绝对路径,或 cd ... || exit 1(配合 set -e 更安全)✓
mysql -uroot -p123456 # ✗ 密码进 Git ✗✗修:从环境变量或配置文件读(《Spring Boot 配置管理与多环境》 的"敏感配置")✓
目标:写一个"能交付"的脚本(不只是能跑)。
步骤:
check-project.sh,核对输出;bash -x 跑一遍:观察变量展开后的真实命令 ✓;fi,用 bash -n 观察报错 ✓;--help:当参数是 -h / --help 时打印用法并 exit 0 ✓;--strict 时把 TODO 也当成失败(体会"规则可配")★;shellcheck(如果能装)跑一遍,看看它会报什么 ✓。验收方式:能说出第 2 步 bash -x 的用处、第 5 步"为什么要可配"。
扩展任务:把 《Linux 服务部署与运维》 的发布步骤写成脚本:
set -euo pipefail、log() 函数、参数校验、幂等;基础题
set -e 会让脚本在"任何命令返回非 0"时立即退出。(对 / 错)$(cmd) 表示 ______;${1:-dev} 表示 ______。代码阅读题
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/${APP}/logs"
rm -rf "$DIR"/*
echo "清理完成"编程题
set -euo pipefail;综合任务
shellcheck)、哪些只能靠评审;rm -rf,应该额外加哪些保护"。提示 1(方向):第 1 题基本是对的,但要注意"某些位置(如 if 条件里)不受 -e 影响"。
提示 2(关键点):第 4 题——APP 如果没定义会怎样?rm -rf "$DIR"/* 会变成什么?
提示 3(接近答案):第 7 题的"脚本算不算代码"——想想脚本出错的代价,以及它是否会被重复执行。
set -e 不生效的位置:
| 位置 | 例子 | 为什么 |
|---|---|---|
if / while 的条件里 | if grep -q x f; then ... | 条件就是"允许失败"的语义 ✓ |
| **` | /&&` 的左边** | |
| 取反 | ! cmd | 同上 ✓ |
还有一个隐蔽的例外 ⚠️:
set -e
count=$(grep -c ERROR app.log) # grep 没匹配到时返回 1,但——
# 在"命令替换 + 赋值"里,某些情况 -e 不生效(取决于 bash 版本与上下文)所以更稳的写法:
count=$(grep -c ERROR app.log || true) # ★ 明确表示"失败也算 0"★ || true 是一个常用技巧:告诉 set -e:"这条命令失败是预期的,别退出" ✓
";命令替换(把命令输出赋给变量);"如果 $1 没传(或为空),就用 dev" ✓三个都是日常高频用法 ✓
补充:
| 写法 | 含义 |
|---|---|
${var:-default} | 未定义或为空 → 用 default ✓ |
${var:=default} | 同上,并且赋值回 var |
${var:?msg} ★ | 未定义或为空 → 打印 msg 并退出 ✓ |
${var:+other} | 已定义 → 用 other(少见) |
不幂等的例子:
| 操作 | 第一次 | 第二次 |
|---|---|---|
mkdir dir | ✓ | ✗ 报错 |
mkdir -p dir ★ | ✓ | ✓ |
echo "x" >> conf | ✓ | ✗ 又追加了一行 |
ln -s target link | ✓ | ✗ 报错(已存在) |
ln -sfn target link ★ | ✓ | ✓ |
★ 记住这两个:mkdir -p 与 ln -sfn ✓
幂等还有一层含义:"重复执行不会产生副作用" —— 如"启动前先检查是否已在运行"(本节的 deploy-demo.sh 就是这么做的 ✓)
第 4 题:两个严重问题
| # | 问题 | 后果 |
|---|---|---|
| 1 | APP 未定义(且 set -u 会拦截 ✓) | 如果没有 -u:DIR="/opt//logs" → rm -rf /opt//logs/* ✗ |
| 2 | rm -rf "$DIR"/* ✗✗ | 如果 DIR 因某种原因为空 → rm -rf /* ✗✗✗ |
★ 第 2 条是"删库跑路"级别的隐患 ✓
为什么会变成空:
DIR="/opt/${APP}/logs"
# 如果 APP 未定义 → /opt//logs(还不是空)
# 但如果写成:DIR="${APP_DIR}/logs" 且 APP_DIR 未定义 → "/logs"
# 或者:DIR="${1}" 没传 → ""
rm -rf ""/* # = rm -rf /* ✗✗✗注意:**即使 set -u 能拦截"未定义变量",它拦截不了"变量被定义为空字符串" ⚠️
APP="" # 定义了,但是空
DIR="/opt/${APP}/logs" # 不报错(因为 APP 已定义)所以 -u 不是万能的 ✓ ——还需要显式检查 ★
第 5 题:安全版本
#!/usr/bin/env bash
set -euo pipefail
APP="${1:?用法:$0 <应用名>}"
# ★ ① 显式校验非空(不只是"已定义")
if [ -z "$APP" ]; then
echo "✗ 应用名不能为空" >&2
exit 1
fi
# ★ ② 校验目录存在且在预期范围内(白名单思路)★
DIR="/opt/$APP/logs"
if [ ! -d "$DIR" ]; then
echo "✗ 目录不存在:$DIR" >&2
exit 1
fi
# ★ ③ 再做一次"安全网":确认路径以 /opt/ 开头
case "$DIR" in
/opt/*) ;;
*)
echo "✗ 拒绝清理非 /opt 下的路径:$DIR" >&2
exit 1
;;
esac
# ★ ④ 用 -mtime 限定"只删旧的",而不是删全部
find "$DIR" -type f -name '*.log' -mtime +7 -delete
echo "清理完成:7 天前的日志"五层保护 ★:
| # | 保护 | 防什么 |
|---|---|---|
| 1 | set -u | 未定义变量 ✗ |
| 2 | ${1:?} | 参数缺失 ✗ |
| 3 | [ -z ] 检查 | 变量为空 ⚠️ |
| 4 | case 白名单 ★★ | 路径不在预期范围 ✗✗ |
| 5 | -mtime +7 而不是 * ★ | 误删还在用的文件 ✗ |
★ 第 4 条最重要:"白名单"思路又一次出现 ✓
与其"检查路径有没有问题",不如"确认它在允许的范围内" ✓
这与 《Git Flow 与团队协作》 的"默认拒绝 + 白名单"、《Filter、Listener 与会话管理》 的鉴权策略完全一致 ✓
第 5 条的额外价值:"只删 7 天前"比"删全部"安全得多 ——即使误执行,损失也有限 ✓
(这也是"降低影响面"的思路,呼应 《Linux 文件、用户与权限管理》 综合任务的"假定失守") ✓
#!/usr/bin/env bash
# analyze-log.sh —— 日志分析
set -euo pipefail
LOG="${1:?用法:$0 <日志文件> [起始时间,如 2026-09-21]}"
SINCE="${2:-}"
# ★ 参数校验:文件必须存在
if [ ! -f "$LOG" ]; then
echo "✗ 文件不存在:$LOG" >&2
exit 1
fi
log() { echo "[$(date '+%F %T')] $*" >&2; }
# ① 错误数
count_error() {
grep -c 'ERROR' "$LOG" || true # ★ || true:没匹配到返回 0 而不是失败
}
# ② 最多的 5 种错误(按第 4 列归类,可按实际日志格式调整列号)
top_errors() {
awk '/ERROR/ {count[$4]++} END {for (k in count) print count[k], k}' "$LOG" \
| sort -rn | head -5
}
# ③ 最慢的 10 个请求(假设最后一列是耗时 ms)
slowest() {
awk '{print $NF, $0}' "$LOG" | sort -rn | head -10 | cut -d' ' -f2-
}
echo "===== 日志分析:$LOG ====="
echo ""
echo "--- 错误总数 ---"
count_error
echo ""
echo "--- 最多的 5 种错误 ---"
top_errors
echo ""
echo "--- 最慢的 10 个请求 ---"
slowest
echo ""
log "分析完成"要点:
| # | 做法 | 说明 |
|---|---|---|
| ① | ${1:?...} + [ -f ] | 参数校验(5.3)★ |
| ② | 三个函数 | 可读、可复用(5.5)✓ |
| ③ | grep -c ... || true | 配合 set -e(没匹配到不算失败)★ |
| ④ | 所有 "$LOG" 加引号 | 路径带空格也不怕(5.2)✓ |
| ⑤ | awk 一次做完筛选+聚合 | 比一串管道更快(《Linux 常用命令与部署》)✓ |
为什么"日志文件路径必须校验存在性"
| 理由 | 说明 |
|---|---|
| ① 快速失败 ★ | 参数错了要在第一秒就说,而不是跑到一半才报"找不到文件" ✗ |
| ② 避免"空结果"被误读 ✗✗ | 如果文件不存在,grep 报错、awk 输出空 → 脚本可能输出"错误数 0" → 你以为"没问题" ✗✗✗ |
| ③ 保护下游 | 如果分析结果被别的脚本使用,空结果会传播 ✗ |
★ 第 ② 条最关键:
"脚本没报错"不等于"脚本做对了" ✓
如果文件不存在,很多命令会"安静地输出空" ——而
set -e未必能拦截(因为命令本身可能返回 0)✗所以显式校验是唯一可靠的方式 ✓
这条经验可以推广:脚本里"输入"都要先校验(文件存在、参数合法、依赖命令可用)✓
"入口校验"与 《参数校验与全局异常处理》 的"参数校验"、《代码规范与自动校验》 的"规范检查"是同一件事: 在边界处拦住错误,而不是让它流到后面 ✓
《Shell 脚本规范》(8 条)
| # | 规范 | 理由 | 示例 |
|---|---|---|---|
| 1 | 开头 set -euo pipefail ★★★ | 防止"失败后继续跑" | set -euo pipefail |
| 2 | 所有变量引用加双引号 ★★ | 防分词(文件名带空格) | rm "$file" |
| 3 | 参数用 ${n:?} 校验 ★ | 错参数要立刻拒绝 | ENV="${1:?用法:...}" |
| 4 | 幂等 ★ | 失败重跑是常态 | mkdir -p / ln -sfn |
| 5 | log() 带时间戳 ★ | 事后可复盘 | log() { echo "[$(date '+%F %T')] $*"; } |
| 6 | 用绝对路径(或先 cd 再校验) | 相对路径依赖"当前目录" ✗ | /opt/app/start.sh |
| 7 | 禁用 rm -rf $VAR/* ★★ | 变量为空 = 灾难 ✗✗✗ | 见第 8 条 |
| 8 | 危险操作要"白名单 + 确认 + 备份" ★★★ | 不可逆操作必须多重保护 | 见下 |
| 9 | 不硬编码密码 | 进 Git 就泄漏 ✗ | 从环境变量读 |
| 10 | 用函数组织,主流程清晰 | 可读 = 可维护 | main() { ... } |
哪些能被工具检查
| 规范 | shellcheck 能查吗 |
|---|---|
| 变量加引号 | ✅ 能(SC2086)★ |
$? 用法 | ✅ 能(SC2181) |
| 语法错误 | ✅ 能 |
set -euo pipefail | ⚠️ 部分(能提示"未设置"的某些情况,但不能强制) |
| 幂等 | ❌ 不能(需要人判断)✗ |
危险 rm | ⚠️ 部分(能提示"变量可能为空",但判断不了业务意图) |
| 不硬编码密码 | ❌ 不能 ✗ |
★ 结论:工具能查"形式",查不了"意图" ✓ ——所以"规范检查(CI)+ 人工 review"都要有 ✓
存放与评审
| 项 | 做法 |
|---|---|
| 存放 | 进 Git(如 scripts/ 目录),不放在服务器某个人的家目录 ✗ |
| 命名 | 动词开头(deploy.sh / backup.sh),带 .sh 后缀 ✓ |
| README | 每个脚本顶部注释:用途、用法、作者、注意事项 ★ |
| 评审 | 脚本与代码一样要 PR + review(见下) |
| 权限 | 服务器上脚本属主正确、权限 750,不要 777 ✗ |
"脚本算不算代码" ★
答:算 ✓
| 理由 | 说明 |
|---|---|
| ① 出错代价一样大 ✗✗ | 一个 rm -rf 的破坏性不亚于一段有 bug 的业务代码 |
| ② 会被反复执行 | 与"一次性命令"不同 —— 脚本的错误会重复发生 ✗ |
| ③ 会被他人使用 | 别人不知道你的"隐含前提"(如"必须在 /opt 下运行")✗ |
| ④ 需要演进 | 环境变了、需求变了 → 脚本也要改 ✓ |
所以脚本应该:
| 要 | 说明 |
|---|---|
| 进 Git ✓ | 版本化、可追溯 |
| 要 review ★ | 尤其是含 rm / kill / 数据库操作的 ✓ |
| 要有测试(至少"干跑"验证) | bash -n(语法)+ 在测试环境执行 ✓ |
要有 --dry-run ★★ | 危险脚本应该支持"只打印将要做什么" ✓✓ |
★ --dry-run 是危险脚本的标配 ✓
DRY_RUN="${DRY_RUN:-0}"
run() {
if [ "$DRY_RUN" = "1" ]; then
echo "[dry-run] $*"
else
"$@"
fi
}
run rm -f "$file" # ★ 真正执行前,可以 DRY_RUN=1 先看看会删什么这与 《Git 分支、合并与回退》 的"--force-with-lease"、《Linux 文件、用户与权限管理》 的"打包成功再删"是同一种谨慎: 在不可逆操作前,给自己一个"看看会怎样"的机会 ✓
会 rm -rf 的脚本要加哪些保护 ★★★
| # | 保护 | 说明 |
|---|---|---|
| 1 | 白名单校验路径 ★★ | case "$DIR" in /opt/*) ;; *) exit 1 ;; esac |
| 2 | --dry-run ★★ | 先看会删什么 |
| 3 | 限定条件(-mtime +7、-name '*.log') | 不删"全部" ✓ |
| 4 | 先备份 / 先移入待删区 | mv 到 /tmp/trash 而不是直接删 ★ |
| 5 | 打印将要删的清单并确认 | read -p "确认删除?[y/N]" ⚠️(自动化脚本里要允许跳过确认) |
| 6 | 记录日志 | 删了什么、什么时候、谁执行的 ✓ |
| 7 | set -u | 防变量未定义 ✓ |
★ 第 4 条最实用:用 mv 到一个"待删目录",过几天再真正清理 ✓
这样即使删错了,也有几天窗口能捞回来 ✓
这是"回收站"的思路 ——而
rm -rf没有回收站 ✗
一句话总结本任务:
脚本的规范、review、保护机制,与业务代码是同一套工程纪律 ✓
因为它们的出错代价相同,而"这只是个脚本"这种想法,恰恰是事故的开始 ✗
这与 《代码规范与自动校验》 的"规范检查接 CI"、《Git Flow 与团队协作》 的"分支保护"、《Linux 文件、用户与权限管理》 的"最小权限"共享同一条设计哲学: 用机制代替注意力 ✓
chmod +x);$1 / $# / ${1:-默认} / ${1:?报错};$(cmd)、$((算术));set -euo pipefail 每一项的作用,以及什么时候可以不用 -e ★★★;bash -n / bash -x / shellcheck;rm -rf 的脚本需要哪些额外保护 ★★。《进程与端口排查》:服务"起不来"或"连不上"时怎么查。
暂无