Shell实战案列

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

可根据需要增加 uptimeuname -adf -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 模拟处理);实际可改成 cpcurl 等。

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_backupdo_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 1exit 2,便于被调用方判断
函数 把重复逻辑封装成函数(如 log),主流程更清晰
安全 删除、覆盖前可 先打印再 read 确认,或加 -i 交互

十三、建议练习顺序

  1. 先跑通案例一(备份)案例四(系统信息),熟悉变量、命令替换、参数。
  2. 再做案例二(日志统计)案例八(按行读文件),熟悉管道、重定向、循环。
  3. 然后做案例五(交互)案例九(菜单),熟悉 readcase、函数。
  4. 最后做案例六(清理)案例七(磁盘)案例十(综合),把参数、判断、循环、函数串起来。

可结合你已学的 Shell流程控制.mdShell函数.mdShell变量.mdShell传递参数.md 等,对照脚本里的每一行加深理解。多改一改参数、路径、条件,再运行看结果,进步会很快。


文档以 Bash 为准;部分命令(如 df、tar、find)在不同系统上选项可能略有差异。

发表评论