Shell 实战案例详解(新手详细版)
本文档面向零基础新手,通过多个完整小项目串联变量、条件、循环、函数、重定向、常用命令等,每个案例都配有完整脚本、逐行说明和运行示例。
一、写脚本前的几个习惯
1.1 第一行:Shebang
脚本第一行写 #!/bin/bash(或 #!/usr/bin/env bash),表示用 Bash 来执行这个文件。
#!/bin/bash
1.2 注释
用 # 写注释,方便以后自己和别人看懂。
# 这是注释
echo "hello" # 行尾也可以写注释
1.3 出错就退出:set -e(可选)
set -e 表示:某条命令失败(退出码非 0)时,脚本立即退出,避免继续用错误结果往下跑。
#!/bin/bash
set -e
# 后面任何命令失败都会退出
1.4 使用未定义变量时报错:set -u(可选)
set -u 表示:用到未定义的变量就报错并退出,避免误用空变量。
set -u
echo "$undefined_var" # 会报错并退出
二、案例一:带日期的简单备份脚本
2.1 需求
把指定目录(或当前目录)打包成 tar.gz,文件名里带当前日期,方便保留多份备份。
2.2 完整脚本
#!/bin/bash
# 功能:把目录打包成 目录名_日期.tar.gz
# 若未传参数,用当前目录
SOURCE="${1:-.}"
# 取目录名(去掉路径最后的 / 再取最后一段)
DIRNAME=$(basename "$SOURCE")
DATE=$(date +%Y%m%d)
OUTPUT="${DIRNAME}_${DATE}.tar.gz"
echo "正在备份: $SOURCE -> $OUTPUT"
tar -czvf "$OUTPUT" "$SOURCE"
echo "完成: $OUTPUT"
2.3 逐段说明
| 行 | 说明 |
|---|---|
SOURCE="${1:-.}" |
第 1 个参数为要备份的路径;没有则用当前目录 . |
DIRNAME=$(basename "$SOURCE") |
取目录名,如 /home/user/docs → docs |
DATE=$(date +%Y%m%d) |
当前日期,如 20250225 |
OUTPUT="..." |
备份文件名,如 docs_20250225.tar.gz |
tar -czvf ... |
打包并压缩;-c 创建,-z gzip,-v 显示过程,-f 指定文件名 |
2.4 使用方法
chmod +x backup.sh
./backup.sh
# 备份当前目录
./backup.sh /home/user/docs
# 备份 /home/user/docs
三、案例二:统计日志里的状态码(简单分析)
3.1 需求
假设有一份 access.log,每行末尾有 HTTP 状态码(如 200、404),统计各状态码出现次数并输出。
3.2 思路
用 awk 取最后一列(状态码),用 sort | uniq -c 计数,再 sort -rn 按次数从高到低排。
3.3 完整脚本
#!/bin/bash
# 功能:统计日志文件最后一列(如状态码)的出现次数
LOG_FILE="${1:-access.log}"
if [ ! -f "$LOG_FILE" ]; then
echo "文件不存在: $LOG_FILE" >&2
exit 1
fi
echo "=== 状态码统计(文件: $LOG_FILE)==="
awk '{print $NF}' "$LOG_FILE" | sort | uniq -c | sort -rn
3.4 逐段说明
| 行 | 说明 |
|---|---|
LOG_FILE="${1:-access.log}" |
第 1 个参数为日志路径,缺省为 access.log |
[ ! -f "$LOG_FILE" ] |
若文件不存在则报错并退出 |
awk '{print $NF}' |
打印每行最后一列(NF=列数,$NF=最后一列) |
sort | uniq -c |
排序后统计每行重复次数 |
sort -rn |
按数字倒序(-r 倒序,-n 按数字) |
3.5 使用示例
./count_status.sh
./count_status.sh /var/log/nginx/access.log
四、案例三:批量重命名(按序号)
4.1 需求
把当前目录下所有 .txt 文件改名为 file_001.txt、file_002.txt、…,便于统一编号。
4.2 完整脚本
#!/bin/bash
# 功能:把当前目录下 .txt 文件重命名为 file_001.txt, file_002.txt, ...
i=1
for f in *.txt; do
[ -f "$f" ] || continue
newname=$(printf "file_%03d.txt" "$i")
echo "重命名: $f -> $newname"
mv "$f" "$newname"
i=$(( i + 1 ))
done
echo "共处理 $(( i - 1 )) 个文件"
4.3 逐段说明
| 行 | 说明 |
|---|---|
for f in *.txt |
遍历当前目录下所有 .txt(无匹配时 f 为字面 *.txt) |
[ -f "$f" ] || continue |
若不是普通文件就跳过(避免无匹配时误操作) |
printf "file_%03d.txt" "$i" |
生成 file_001、file_002(%03d 表示 3 位数字,前导 0) |
mv "$f" "$newname" |
重命名 |
i=$(( i + 1 )) |
序号加 1 |
4.4 使用注意
- 建议先在测试目录里试,确认无误再在重要目录用。
- 若已有 file_001.txt 等,可能覆盖;可先判断 newname 是否存在再决定是否跳过或换名。
五、案例四:打印简单系统信息
5.1 需求
输出主机名、当前用户、磁盘使用、内存使用等,便于快速看环境。
5.2 完整脚本
#!/bin/bash
# 功能:打印简单系统信息
echo "========== 系统信息 =========="
echo "主机名: $(hostname)"
echo "当前用户: $USER"
echo "当前目录: $PWD"
echo "日期时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
echo "--- 磁盘使用(根分区)---"
df -h / | tail -1
echo ""
echo "--- 内存使用 ---"
free -h | head -2
echo "=============================="
5.3 使用
./sysinfo.sh
可根据需要增加 uptime、uname -a、df -h 等命令。
六、案例五:带提示和校验的交互脚本
6.1 需求
提示用户输入文件名,若文件存在则显示前 5 行;若不存在或未输入则提示并退出。
6.2 完整脚本
#!/bin/bash
# 功能:输入文件名,显示前 5 行;带简单校验
printf "请输入文件名: "
read -r filename
if [ -z "$filename" ]; then
echo "未输入文件名" >&2
exit 1
fi
if [ ! -f "$filename" ]; then
echo "文件不存在: $filename" >&2
exit 1
fi
echo "=== 前 5 行 ==="
head -5 "$filename"
6.3 逐段说明
| 行 | 说明 |
|---|---|
read -r filename |
读一行到变量;-r 不把 当转义 |
[ -z "$filename" ] |
若变量为空则报错退出 |
[ ! -f "$filename" ] |
若不是普通文件则报错退出 |
>&2 |
错误信息写到标准错误,便于重定向时区分 |
七、案例六:清理 N 天前的旧备份(保留最近几份)
7.1 需求
在指定目录下删除超过 7 天的 .tar.gz 备份文件,避免占满磁盘。
7.2 完整脚本
#!/bin/bash
# 功能:删除指定目录下超过 N 天的 .tar.gz 文件
BACKUP_DIR="${1:-.}"
DAYS="${2:-7}"
if [ ! -d "$BACKUP_DIR" ]; then
echo "目录不存在: $BACKUP_DIR" >&2
exit 1
fi
echo "目录: $BACKUP_DIR, 删除 ${DAYS} 天前的 .tar.gz"
find "$BACKUP_DIR" -maxdepth 1 -name "*.tar.gz" -mtime +"$DAYS" -print
read -p "确认删除以上文件?(y/N) " confirm
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
find "$BACKUP_DIR" -maxdepth 1 -name "*.tar.gz" -mtime +"$DAYS" -exec rm -v {} ;
else
echo "已取消"
fi
7.3 逐段说明
| 行 | 说明 |
|---|---|
DAYS="${2:-7}" |
第 2 个参数为“超过多少天”,默认 7 |
find ... -mtime +"$DAYS" |
修改时间在 DAYS 天之前的文件 |
-maxdepth 1 |
只查当前目录,不递归子目录 |
-print |
先只打印,不删;确认后再执行带 -exec rm 的 find |
read -p "..." confirm |
带提示的读入,用户确认后才删 |
7.4 使用示例
./clean_old_backups.sh /backup
# 删除 /backup 下 7 天前的 .tar.gz
./clean_old_backups.sh /backup 30
# 删除 30 天前的
八、案例七:检查磁盘空间,不足则告警
8.1 需求
检查根分区 / 的使用率,若超过 90% 则在屏幕打印告警(也可后续改成发邮件或写日志)。
8.2 完整脚本
#!/bin/bash
# 功能:检查根分区使用率,超过阈值则告警
THRESHOLD="${1:-90}"
USE=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
if [ -z "$USE" ]; then
echo "无法获取磁盘使用率" >&2
exit 1
fi
if [ "$USE" -ge "$THRESHOLD" ]; then
echo "警告: 根分区使用率 ${USE}% 超过阈值 ${THRESHOLD}%" >&2
df -h /
exit 2
else
echo "正常: 根分区使用率 ${USE}%"
fi
8.3 逐段说明
| 行 | 说明 |
|---|---|
df / | tail -1 |
取 df 输出的最后一行(根分区那一行) |
awk '{print $5}' |
第 5 列是使用率,如 85% |
tr -d '%' |
去掉 %,得到数字 85 |
[ "$USE" -ge "$THRESHOLD" ] |
数值比较,大于等于则告警 |
exit 2 |
用非 0 退出码表示“异常”,便于被其他脚本或监控调用 |
8.4 使用
./check_disk.sh
./check_disk.sh 95
# 阈值改为 95%
九、案例八:按行读文件并处理(批量)
9.1 需求
从文件列表里逐行读路径,对每一行执行同一条命令(如 echo 模拟处理);实际可改成 cp、curl 等。
9.2 完整脚本
#!/bin/bash
# 功能:从文件 list.txt 每行读一个路径,逐条处理
LIST_FILE="${1:-list.txt}"
if [ ! -f "$LIST_FILE" ]; then
echo "文件不存在: $LIST_FILE" >&2
exit 1
fi
while IFS= read -r line; do
[ -z "$line" ] && continue
echo "处理: $line"
# 这里可换成实际命令,例如: cp "$line" /dest/
done < "$LIST_FILE"
9.3 逐段说明
| 行 | 说明 |
|---|---|
while IFS= read -r line |
IFS= 避免去掉行首尾空格;read -r 不转义 |
[ -z "$line" ] && continue |
跳过空行 |
done < "$LIST_FILE" |
从文件重定向到 while 的 read |
9.4 使用
先建 list.txt,每行一个路径,再执行:
./batch_process.sh list.txt
十、案例九:简单“菜单”选择(case + 函数)
10.1 需求
显示菜单,用户输入 1、2、3 选择不同操作(这里用 echo 模拟,实际可调用不同函数或命令)。
10.2 完整脚本
#!/bin/bash
# 功能:简单菜单,根据输入执行不同操作
show_menu() {
echo "1) 备份"
echo "2) 清理"
echo "3) 退出"
}
do_backup() {
echo "执行备份..."
}
do_clean() {
echo "执行清理..."
}
while true; do
show_menu
printf "请选择 [1-3]: "
read -r choice
case "$choice" in
1) do_backup ;;
2) do_clean ;;
3) echo "再见"; exit 0 ;;
*) echo "无效选择" ;;
esac
echo ""
done
10.3 逐段说明
| 部分 | 说明 |
|---|---|
show_menu |
函数:只负责打印菜单 |
do_backup、do_clean |
函数:实际可在这里写备份、清理逻辑 |
while true |
死循环,选 3 时 exit 0 退出 |
case "$choice" in ... esac |
根据用户输入分支执行 |
十一、案例十:综合小脚本(参数 + 函数 + 日志)
11.1 需求
- 接受一个目录参数;
- 在该目录下创建 backup_日期 子目录,并把目录内所有 .conf 复制进去;
- 简单“日志”输出到屏幕(也可重定向到文件)。
11.2 完整脚本
#!/bin/bash
# 功能:在指定目录下创建带日期的备份目录,并复制所有 .conf 进去
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
if [ $# -lt 1 ]; then
echo "用法: $0 <目录>" >&2
exit 1
fi
TARGET="$1"
if [ ! -d "$TARGET" ]; then
log "错误: 目录不存在 $TARGET" >&2
exit 1
fi
DATE=$(date +%Y%m%d)
BACKUP_DIR="$TARGET/backup_$DATE"
mkdir -p "$BACKUP_DIR"
log "创建目录: $BACKUP_DIR"
count=0
for f in "$TARGET"/*.conf; do
[ -f "$f" ] || continue
cp -v "$f" "$BACKUP_DIR/"
count=$(( count + 1 ))
done
log "完成: 复制了 $count 个 .conf 文件到 $BACKUP_DIR"
11.3 逐段说明
| 部分 | 说明 |
|---|---|
log() { ... } |
带时间戳的日志函数,便于统一格式 |
[ $# -lt 1 ] |
参数个数小于 1 则打印用法并退出 |
mkdir -p "$BACKUP_DIR" |
-p 表示已存在不报错,可建多级 |
for f in "$TARGET"/*.conf |
只处理 .conf;无匹配时 f 为字面 *.conf,故用 [ -f “$f” ] 判断 |
11.4 使用
./backup_conf.sh /etc/nginx
# 在 /etc/nginx 下建 backup_20250225,并复制所有 .conf 进去
十二、实战要点小结
| 要点 | 说明 |
|---|---|
| 参数 | 用 ${1:-默认}、$# 判断个数,缺参时提示用法并 exit 1 |
| 校验 | 用 [ -f ]、[ -d ] 检查文件/目录是否存在再操作 |
| 引号 | 变量和路径用 “$var”,避免空或含空格出错 |
| 错误输出 | 提示、错误信息用 >&2,正常输出到 stdout |
| 退出码 | 正常结束 exit 0,异常 exit 1 或 exit 2,便于被调用方判断 |
| 函数 | 把重复逻辑封装成函数(如 log),主流程更清晰 |
| 安全 | 删除、覆盖前可 先打印再 read 确认,或加 -i 交互 |
十三、建议练习顺序
- 先跑通案例一(备份)、案例四(系统信息),熟悉变量、命令替换、参数。
- 再做案例二(日志统计)、案例八(按行读文件),熟悉管道、重定向、循环。
- 然后做案例五(交互)、案例九(菜单),熟悉 read、case、函数。
- 最后做案例六(清理)、案例七(磁盘)、案例十(综合),把参数、判断、循环、函数串起来。
可结合你已学的 Shell流程控制.md、Shell函数.md、Shell变量.md、Shell传递参数.md 等,对照脚本里的每一行加深理解。多改一改参数、路径、条件,再运行看结果,进步会很快。
文档以 Bash 为准;部分命令(如 df、tar、find)在不同系统上选项可能略有差异。