【Day 41】Shell脚本-循环
一、循环类型
Shell 脚本中常用的循环类型有两种:
- for 循环:适合已知遍历范围的场景(如固定列表、序列、文件列表等)。
- while 循环:适合未知循环次数,需通过条件判断终止的场景(如持续监听、逐行处理文件等)。
二、for 循环
1. for 循环的语法
# 语法1 用;分隔取值列表和do,适合操作简短(单条命令)的场景,代码更紧凑。
for 变量 in 取值列表; do执行的操作
done# 语法2 用换行分隔取值列表和do,适合操作复杂(多条命令)的场景,可读性更强
for 变量 in 取值列表
do执行的操作
done
2、执行逻辑
- 初始化遍历:从 “取值列表” 中取出第一个元素,赋值给 “变量”。
- 执行操作:运行do和done之间的所有命令(即 “执行的操作”)。
- 循环迭代:从 “取值列表” 中取出下一个元素,重复步骤 1 和 2。
- 终止循环:当 “取值列表” 中的所有元素都处理完毕后,退出循环,执行done之后的代码。
3、取值列表
在 for 变量 in 取值列表; do ... done 结构中,取值列表可以是多种形式的 “元素集合”,变量则用于接收列表中的每个元素(每次循环取一个元素)。
取值列表本质是 “一系列元素的集合”,元素之间以 IFS(默认是空格、制表符、换行符)分隔。
取值列表的常见形式
(1)直接写死的字符串 / 数字列表(最基础)
直接列举多个元素,用空格分隔,适合已知固定值的场景。
# 示例:遍历字符串列表
for fruit in apple banana "cherry pie" date; doecho "水果:$fruit"
done
# 输出:
# 水果:apple
# 水果:banana
# 水果:cherry pie (带空格的元素需用引号包裹,否则会被拆分成两个元素)
# 水果:date# 示例:遍历数字列表
for num in 1 3 5 7; doecho "奇数:$num"
done
(2)序列(连续的数字 / 字符范围)
通过 {起始..结束} 或 seq 命令生成连续序列,适合循环固定次数。
# 1. {起始..结束}:Bash 内置的序列扩展(无需调用外部命令,高效)
for i in {1..5}; do # 数字序列:1-5echo "第$i次循环"
donefor c in {a..e}; do # 字符序列:a-eecho "字母:$c"
donefor i in {10..1..2}; do # 倒序+步长:10、8、6、4、2(Bash 4.0+支持)echo "倒序偶数:$i"
done# 2. seq 命令:生成序列(兼容更多老版本Shell)
for i in $(seq 1 2 10); do # 从1开始,步长2,到10结束(1、3、5、7、9)echo "步长为2的数:$i"
done
// seq 是 Linux 系统中的一个工具,用于生成连续的整数序列,默认以换行分隔每个数字。
- seq [起始数] [步长] [结束数]
- seq 20 //只指定结束(默认从 1 开始,步长 1)即 seq 20,等价于 seq 1 1 20。。
- seq 5 10 // 指定起始和结束。输出从 5 到 10 的整数:
- seq 2 2 10 //指定步长。从 2 开始,每次加 2,直到不超过 10:
在脚本中的典型用途
seq 常与 for 循环结合,生成遍历范围,例如:
# 循环 20 次,创建 20 个文件
for i in $(seq 20); dotouch /tmp/test_$i.txt
done
这里 $(seq 20) 会展开为 1 2 ... 20,循环变量 i 依次取这些值,实现批量操作
(3)文件名通配符(匹配文件 / 目录)
通过通配符(*、?、[] 等)匹配符合条件的文件 / 目录名,适合批量处理文件。
# 示例1:遍历当前目录下所有.txt文件
for txtfile in *.txt; doecho "处理文本文件:$txtfile"
done# 示例2:遍历/tmp目录下所有以"log_"开头的文件
for logfile in /tmp/log_*; doecho "日志文件:$logfile"
done# 示例3:匹配数字结尾的文件(如file1、file2)
for file in file[0-9]; doecho "数字结尾的文件:$file"
done
(4)命令替换的输出($(命令) 或 \命令 )
将命令的输出作为取值列表,适合动态生成元素(如从文件读取、过滤结果等)。
# 示例1:遍历/etc/passwd中所有用户名(提取第1个字段)
for user in $(awk -F: '{print $1}' /etc/passwd); doecho "系统用户:$user"
done# 示例2:遍历当前目录下的子目录(ls -d */ 列出目录,再去掉末尾的/)
for dir in $(ls -d */ | sed 's:/$::'); doecho "子目录:$dir"
done# 示例3:遍历文件中的每行内容(需确保行内无空格,否则会拆分)
for line in $(cat /tmp/names.txt); do # 若行内有空格,建议用while readecho "姓名:$line"
done
命令替换的输出$(命令) 是否会 “处理空格”,取决于是否用双引号包裹,核心是 Shell“分词机制”:
1、默认情况(不加双引号):
不会直接 “切掉” 空格,但会把空格(以及制表符、换行符,即默认的 IFS)当作 “分隔符”,将输出拆分成多个 “单词”。
例如:命令输出 a b c(含两个空格和一个空格),会被拆成 a、b、c 三个独立元素(多个连续空格会被视为一个分隔符)。
2、加双引号包裹(如 "$(命令)"):
会禁用分词,空格会被完整保留,输出作为一个整体。
例如:命令输出 a b c,用双引号包裹后,会被当作一个完整元素 a b c(空格原样保留)。
(5)数组元素(遍历数组)
遍历数组中的所有元素,适合需要先存储元素再处理的场景。
# 定义数组
fruits=("apple" "banana" "cherry pie" "date")# 遍历数组("${数组名[@]}" 表示所有元素,双引号避免空格拆分)
for fruit in "${fruits[@]}"; doecho "数组中的水果:$fruit"
done
(6)位置参数(脚本 / 函数的输入参数)
遍历脚本或函数的参数($@ 表示所有参数),适合处理动态输入。
# 脚本:接收用户传入的多个文件名,批量处理
# 用法:./process.sh file1.txt file2.txt file3.txt
for file in "$@"; do # "$@" 会保留参数中的空格(如"my file.txt"作为一个元素)echo "处理参数文件:$file"
done
(7)变量展开(使用变量作为列表)
将变量的值作为取值列表(变量中存储多个元素,以空格分隔)。
# 变量存储多个元素(带空格的元素需提前用引号处理)
colors="red green 'blue sky'" # 注意:'blue sky' 用单引号包裹,避免拆分# 遍历变量(需用eval解析引号,否则单引号会被当作普通字符)
for color in $(eval echo $colors); doecho "颜色:$color"
done
# 输出:
# 颜色:red
# 颜色:green
# 颜色:blue sky
示例 1:在 /tmp 目录创建 20 个测试文件(两种命名方式)
#!/bin/bash
# 创建20个随机名称的文件(使用openssl生成10位十六进制字符串)
for i in $(seq 20); dotouch /tmp/$(openssl rand -hex 10)
done# 或创建有序命名的文件(file1到file20)
for i in {1..20}; do # {1..20}是bash的序列扩展,比seq更高效touch /tmp/file${i}
done
示例 2:批量创建用户(带存在性检查和初始密码)
#!/bin/bash
# 批量创建user1到user10,若已存在则提示,否则创建并设置初始密码
for i in {seq 10}; dousername="user${i}"if id "$username" &> /dev/null; thenecho "用户$username已存在,跳过"elseuseradd "$username"echo "123456" | passwd --stdin "$username" &> /dev/null # 设置初始密码echo "用户$username创建成功,初始密码:123456"fi
done
示例 3:从文件读取用户列表批量创建
#!/bin/bash
# 从/tmp/userlist文件读取用户名(每行一个),批量创建用户
# 前提:/tmp/userlist格式为每行一个用户名,如:alice、bob
if [ ! -f "/tmp/userlist" ]; thenecho "错误:/tmp/userlist文件不存在"exit 1
fifor name in $(cat /tmp/userlist); doif id "$name" &> /dev/null; thenecho "用户$name已存在"elseuseradd "$name"echo "用户$name创建完成"fi
done
示例 4:多线程检测网段在线 IP(限制并发数)
#!/bin/bash
# 检测10.11.9.0/24网段在线IP,最多同时运行50个ping进程(避免系统负载过高)
subnet="10.11.9."
max_concurrent=50 # 最大并发数
current=0for i in {1..255}; do((current++))if [ $current -ge $max_concurrent ]; thenwait # 等待当前批次进程结束current=0fi{ip="${subnet}${i}"if ping -c 1 -W 1 "$ip" &> /dev/null; then # 发送1个包,超时1秒echo "Host $ip is up"fi}& # 后台运行
done
wait # 等待剩余后台进程结束
echo "检测完成"
#!/bin/bash
subnet=10.11.7.for i in $(seq 255); do{ip=$subnet$iif ping -c 2 -W 1 $ip &>/dev/null; thenecho "Host $ip is up"fi}&
done
wait
示例 5:检测目录文件差异(补充完整逻辑)
#!/bin/bash
# 对比/tmp/bj和/tmp/sh目录,检测缺失或不一致的文件(完善版)
src_dir="/tmp/bj"
dest_dir="/tmp/sh"# 检查源目录是否存在
if [ ! -d "$src_dir" ]; thenecho "错误:源目录$src_dir不存在"exit 1
fi# 遍历源目录所有文件,对比目标目录
for src_file in $(find "$src_dir" -type f); do# 替换路径中的"bj"为"sh",得到目标文件路径(字符串替换:${变量/旧值/新值})dest_file="${src_file/bj/sh}"if [ ! -e "$dest_file" ]; thenecho "文件缺失:$dest_file"else# 对比MD5值(忽略文件名,只比较内容)src_md5=$(md5sum "$src_file" | awk '{print $1}')dest_md5=$(md5sum "$dest_file" | awk '{print $1}')if [ "$src_md5" != "$dest_md5" ]; thenecho "内容不一致:$dest_file"fifi
done # 补充原脚本缺失的done
示例6:用循环命令统计 /etc/passwd 中使用 /bin/bash 和 /sbin/nologin 作为登录 shell 的用户数量
①(按行号逐行提取)
#!/bin/bash
bash_count=0
nologin_count=0
# 先获取文件总行数,再按行号循环,逐行提取shell字段
number=$(wc -l /etc/passwd |awk '{print $1}') #获取总行数
for i in $(seq $number); do #循环行号(1到总行数)shell=$(head -n $i /etc/passwd | tail -n 1 | awk -F: '{print $7}')# 判断计数if [ "$shell" == "/bin/bash" ]; thenlet bash_count++elif [ "$shell" == "/sbin/nologin" ]; thenlet nologin_count++fi
done
echo "bash用户数量: $bash_count"
②(直接提取 shell 字段)更优
#!/bin/bash
bash_count=0
nologin_count=0
# 用awk一次性提取所有用户的shell字段(第7列),然后遍历这些字段
for shell in $(awk -F: '{print $7}' /etc/passwd); do# 判断shell类型并计数if [ "$shell" == "/bin/bash" ]; thenlet bash_count++elif [ "$shell" == "/sbin/nologin" ]; thenlet nologin_count++fi
done
echo "bash用户数量: $bash_count"
echo "nologin用户数量: $nologin_count"
1. 为什么 for line in $(cat /etc/passwd) 会出错?
cat /etc/passwd 会把文件内容全输出,但输出的是 “一堆字符串”(包含换行和行内空格)。
Shell 会按默认规则(空格、换行都算分隔符)把这堆字符串拆成 “小碎片”。
比如有一行 Zhang San:...(中间有空格),会被拆成 Zhang 和 San:... 两个碎片,for 循环里的 line 拿到的是碎片,不是完整行。
碎片没有足够的字段(比如原本 7 个字段,碎片可能只有 3 个),后续用 cut 取第 7 个字段自然会错。
2. 为什么 for shell in $(awk ...) 没问题?
awk -F: '{print $7}' 会先把 整行内容 按冒号拆分(不管行里有没有空格),正确提取第 7 个字段(比如 /bin/bash)。
它输出的结果是 “一个个独立的字段值”(比如 /bin/bash、/sbin/nologin 等),这些值本身几乎没有空格,不会被 Shell 拆成碎片。
所以 for 循环里的 shell 拿到的是完整的字段值,自然不会错。
示例7:/opt/data下的1.txt~10.txt会被重命名为1.html~10.html,
#!/bin/bash
for file_name in $(find /opt/data -name "*.txt"); do# 用echo输出文件名,通过管道给awk按"."分割,提取第一个字段(前缀)prefix=$(echo "$file_name" | awk -F. '{print $1}')new_file_name="$prefix.html"mv "$file_name" "$new_file_name"
done
#!/bin/bash
for file_name in $(find /opt/data -name "*.txt"); donew_file_name=${file_name/txt/html}mv "$file_name" "$new_file_name"
done
三、while 循环
1. 基本语法
while 条件判断; do执行的操作# (通常需要修改条件变量,避免死循环)
done
示例:用 while 创建 10 个随机文件
#!/bin/bash
i=1 # 初始化计数器
while [ $i -le 10 ]; do # 条件:i小于等于10touch /tmp/$(openssl rand -hex 8)let i++ # 计数器自增(等价于 i=$((i+1)))
done
echo "10个文件创建完成"
2. while true 循环(无限循环)(死循环)
适用于需要持续运行的场景(如监控脚本、服务守护进程)。
#!/bin/bash
# 每2秒打印一次系统负载(按Ctrl+C终止)
while true; do # true恒为真,循环永不结束echo "当前系统负载:$(uptime | awk '{print $10 $11 $12}')"sleep 2 # 休眠2秒,降低资源占用
done
3. while read line 循环(逐行处理)
通过read命令逐行读取输入(文件或管道输出),line变量代表当前行内容。
#!/bin/bash
while read line; do 执行操作
done < 文件名称
3.1 遍历文件内容
#!/bin/bash
# 从/etc/passwd读取系统用户,导入MySQL数据库(带错误处理)
# 前提:已创建数据库it和表passwd(字段:name,password,uid,gid,comment,home_dir,shell)
db_user="root"
db_pass="WWW.1.com"
db_name="it"# 检查MySQL连接
if ! mysql -u"$db_user" -p"$db_pass" -e "use $db_name" &> /dev/null; thenecho "错误:MySQL连接失败(用户名/密码/数据库错误)"exit 1
fi# 逐行读取/etc/passwd(格式:name:password:uid:gid:comment:home:shell)
while read line; doname=$(echo "$line" | awk -F: '{print $1}')password=$(echo "$line" | awk -F: '{print $2}')uid=$(echo "$line" | awk -F: '{print $3}')gid=$(echo "$line" | awk -F: '{print $4}')comment=$(echo "$line" | awk -F: '{print $5}' | sed "s/'/\\\'/g") # 转义单引号,避免SQL错误home_dir=$(echo "$line" | awk -F: '{print $6}')shell=$(echo "$line" | awk -F: '{print $7}')# 插入数据库mysql -u"$db_user" -p"$db_pass" -e \"insert into $db_name.passwd values('$name','$password',$uid,$gid,'$comment','$home_dir','$shell')" &> /dev/nullif [ $? -eq 0 ]; thenecho "导入成功:$name"elseecho "导入失败:$name"fi
done < /etc/passwd # 通过重定向指定输入文件
3.2 处理命令输出(管道方式)
命令|while read line;do执行操作done
例子:
# 检测磁盘使用率>20%的分区(兼容不同系统df输出格式
#!/bin/bash
# 检测磁盘使用率>20%的分区(兼容不同系统df输出格式)
# df -hT:显示文件系统类型、容量、使用率等(-h:人类可读单位,-T:显示类型)
df -hT | grep -vE "tmpfs|loop" | while read line; do # 排除tmpfs和loop设备# 提取关键信息(不同系统列顺序可能不同,通过$NF定位挂载点)usage=$(echo "$line" | awk '{print $(NF-1)}' | sed 's/%//') # 使用率(去掉%)disk_type=$(echo "$line" | awk '{print $2}') # 文件系统类型total_size=$(echo "$line" | awk '{print $3}') # 总容量free_size=$(echo "$line" | awk '{print $5}') # 剩余容量mount_point=$(echo "$line" | awk '{print $NF}') # 挂载点(最后一列)if [ "$usage" -gt 20 ]; then # 比较数字(已去掉%)echo "磁盘预警:$mount_point($disk_type)"echo " 总容量:$total_size,剩余:$free_size,使用率:${usage}%"fi
done # 补充原脚本缺失的done
例子:统计tcp端口状态netstat -antp处于listen状态的established建立连接的有几个
#!/bin/bash
listen_count=0
established_count=0
while read -r line; dostate=$(echo "$line" | awk '{print $6}')if [ "$state" = "LISTEN" ]; then((listen_count++))elif [ "$state" = "ESTABLISHED" ]; then((established_count++))fi
done < <(netstat -antp | grep '^tcp' ) # 只保留TCP连接(排除标题和其他协议)
echo "处于LISTEN状态的TCP端口数量是:$listen_count"
echo "处于ESTABLISHED状态的TCP端口数量是:$established_count"
四、中断循环的操作
1. break:终止整个循环
当满足条件时,直接退出当前循环,不再执行后续迭代。
示例:找到第 3 个文件后停止遍历
#!/bin/bash
# 遍历/tmp目录,找到3个文件后停止
count=0
for file in /tmp/*; doif [ -f "$file" ]; then # 只统计文件(排除目录)echo "找到文件:$file"((count++))if [ $count -eq 3 ]; thenecho "已找到3个文件,停止遍历"break # 终止整个循环fifi
done
2. continue:跳过本次循环
当满足条件时,跳过当前迭代的剩余操作,直接进入下一次循环。
示例:处理日志时跳过空
#!/bin/bash
# 遍历日志文件,跳过空行并打印非空行内容
log_file="/var/log/messages"
if [ ! -f "$log_file" ]; thenecho "错误:日志文件$log_file不存在"exit 1
fiwhile read line; doif [ -z "$line" ]; then # 若行为空(-z:字符串长度为0)continue # 跳过本次循环,不执行后续echofiecho "日志内容:$line"
done < "$log_file"
综合示例:显示前 5 个系统用户(UID 1-199)
#!/bin/bash
# 从/etc/passwd提取前5个系统用户(UID 1-199)
count=1 # 计数器:记录已找到的用户数量while read line; douid=$(echo "$line" | awk -F: '{print $3}')# 判断是否为系统用户(UID 1-199)if [ "$uid" -ge 1 ] && [ "$uid" -le 199 ]; thenusername=$(echo "$line" | awk -F: '{print $1}')echo "系统用户$count:$username(UID:$uid)"((count++)) # 计数器自增fi# 找到5个后终止循环if [ $count -gt 5 ]; thenbreakfi
done < /etc/passwd
总结
- for 循环:优先用于已知范围的遍历(如序列、文件列表、固定集合),语法简洁。
- while 循环:适合条件驱动的场景(如无限循环、逐行处理、动态条件判断)。
- break/continue:灵活控制循环流程,break 终止整个循环,continue 跳过当前迭代。
需求一:查找硬盘使用率 >10% 的信息
#!/bin/bash
echo "硬盘使用率不超过70%的信息:"
df -hT | tail -n +2 | while read -r line; do# 按实际列顺序提取(以下为默认列顺序,若不同请修改$数字)name=$(echo "$line" | awk '{print $1}') # 磁盘名称(第1列)total=$(echo "$line" | awk '{print $3}') # 总容量(第3列)free=$(echo "$line" | awk '{print $5}') # 可用容量(第5列)usage=$(echo "$line" | awk '{print $6}' | sed 's/%//') # 使用率(第6列,去掉%)if [ "$usage" -gt 10 ]; thenecho "磁盘名称:$name"echo "总容量:$total"echo "可用容量:$free"echo "当前使用率:${usage}%"echo "-------------------"fi
done
需求二:找出近 10 分钟启动的进程
(1)
#!/bin/bash
# 获取当前时间的时间戳
now=$(date +%s)
for proc_id in $(ps -eo pid | sed '1d'); do
# 针对当前进程 ID(proc_id),获取其完整启动时间:= 表示去掉列标题(只保留时间值)。proc_start_time=$(ps -p $proc_id -o lstart=)proc_start_timestap=$(date -d "$proc_start_time" +%s)# 判断10分钟内启动的进程 let offset=now-proc_start_timestap if [ $offset -lt 600 ]; thenecho "进程ID:$proc_id, 进程名称: $(ps -p ${proc_id} -o comm=)"fi
done
(2)遍历所有进程→获取启动时间→转换为时间戳→计算与当前时间的差值
#!/bin/bash
# 获取当前时间戳(秒)
now=$(date +%s)
# 遍历所有进程ID(1d 表示删除第一行)逐行读取处理后的进程 ID,存到变量 id 中,循环处理每个进程。
ps -eo pid | sed '1d' | while read -r id; do# 通过进程ID获取启动时间(lstart= 去掉列标题)#-p "$id":指定只查看该 PID 的进程。#-o lstart=:lstart 表示输出完整启动时间,= 表示去掉列标题(只保留时间值)。start_time=$(ps -p "$id" -o lstart= 2>/dev/null)# continue:如果为空,跳过当前进程,直接处理下一个 PID。if [ -z "$start_time" ]; thencontinuefi# 将启动时间转换为时间戳start_timestamp=$(date -d "$start_time" +%s 2>/dev/null)# 如果 start_timestamp 为空(转换失败),跳过当前进程,处理下一个。if [ -z "$start_timestamp" ]; thencontinuefi#$((...)):bash 中的算术运算,计算与当前时间戳的差值(秒)offset=$((now - start_timestamp))# 筛选10分钟内(600秒)启动的进程if [ "$offset" -lt 600 ]; then# 获取进程名称comm=$(ps -p "$id" -o comm=)echo "进程ID:$id,进程名称:$comm,启动时间:$start_time"fi
done
需求三:将 /etc/passwd 文件内容按行写入数据库(以 MySQL 为例)
说明:需提前创建数据库和表:
CREATE TABLE passwd_info (username VARCHAR(50) NOT NULL PRIMARY KEY, -- 用户名(主键,唯一)password VARCHAR(10) NOT NULL, -- 密码占位符(通常为x)uid INT NOT NULL, -- 用户IDgid INT NOT NULL, -- 组IDcomment VARCHAR(255), -- 注释信息(可能包含用户全名等)home_dir VARCHAR(100) NOT NULL, -- 家目录路径login_shell VARCHAR(100) NOT NULL -- 登录shell路径
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#!/bin/bash
# 将/etc/passwd内容按行写入MySQL数据库(需提前创建表)# 数据库配置(请根据实际情况修改)
db_user="root"
db_pass="your_password"
db_name="sys_db"
table_name="passwd_info"# 检查数据库连接
if ! mysql -u"$db_user" -p"$db_pass" -e "use $db_name" &>/dev/null; thenecho "错误:数据库连接失败,请检查账号密码"exit 1
fi# 逐行读取/etc/passwd并插入数据库
echo "开始写入/etc/passwd到数据库..."
while read -r line; do# 分割/etc/passwd字段(格式:name:password:uid:gid:comment:home:shell)name=$(echo "$line" | awk -F: '{print $1}')password=$(echo "$line" | awk -F: '{print $2}')uid=$(echo "$line" | awk -F: '{print $3}')gid=$(echo "$line" | awk -F: '{print $4}')comment=$(echo "$line" | awk -F: '{print $5}' | sed "s/'/\\\'/g") # 转义单引号home=$(echo "$line" | awk -F: '{print $6}')shell=$(echo "$line" | awk -F: '{print $7}')# 插入数据库mysql -u"$db_user" -p"$db_pass" -e \"INSERT INTO $db_name.$table_name (name, password, uid, gid, comment, home, shell) VALUES ('$name', '$password', $uid, $gid, '$comment', '$home', '$shell')" &>/dev/nullif [ $? -eq 0 ]; thenecho "成功写入:$name"elseecho "写入失败:$name"fi
done < /etc/passwdecho "写入完成"
需求四:查找系统中前 3 个系统用户(UID 1-999 间)
#!/bin/bash
# 查找UID在1-999之间的前3个系统用户count=0 # 计数器:记录找到的系统用户数量echo "前3个系统用户(UID 1-999):"
echo "用户名 | UID"
echo "------------------------"# 逐行读取/etc/passwd,筛选符合条件的用户
while read -r line; do# 提取用户名和UIDusername=$(echo "$line" | awk -F: '{print $1}')uid=$(echo "$line" | awk -F: '{print $3}')# 判断UID是否在1-999之间if [ "$uid" -ge 1 ] && [ "$uid" -le 999 ]; then((count++))echo "$username | $uid"# 找到3个后停止循环if [ $count -eq 3 ]; thenbreakfifi
done < /etc/passwd
需求五:检测目录间文件差异(以 /src 和 /dest 为例)
#!/bin/bash
# 检测两个目录间的文件差异(缺失/内容不一致)
src="/Sylvia" # 源目录(请修改为实际路径)
dest="/Angelina" # 目标目录(请修改为实际路径)# 检查目录是否存在
if [ ! -d "$src" ]; thenecho "错误:源目录$src存在"exit 1
fi
if [ ! -d "$dest" ]; thenecho "错误:目标目录$dest不存在"exit 1
fi
# 遍历源目录所有文件,对比目标目录
find "$src" -type f | while read -r src_file; do# 转换为目标目录对应路径(替换/src为/dest)dest_file="${src_file/$src/$dest}"# 检查目标文件是否缺失if [ ! -f "$dest_file" ]; thenecho "缺失文件:$dest_file"continuefi# 比较文件内容(MD5值)src_md5=$(md5sum "$src_file" | awk '{print $1}')dest_md5=$(md5sum "$dest_file" | awk '{print $1}')if [ "$src_md5" != "$dest_md5" ]; thenecho "内容不一致:$dest_file"fi
doneecho "检测完成"