Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现 循环和流程控制是 Shell 脚本实现复杂逻辑的核心机制。无论是批量处理文件、迭代数组元素,还是实现条件分支,都离不开灵活的流程控制结构。本文将系统介绍 Shell 中的循环类型、条件判断和流程控制技巧,帮助你编写高效、清晰的脚本逻辑。 一、条件判断:Shell 中的逻辑分支 条件判断是流程控制的基础,Shell 提供了多种方式实现条件检查:

  1. 基本 if 语句结构 bash #!/bin/bash

基本 if 结构

age=25 if [ $age -ge 18 ]; then echo "已成年" fi

if-else 结构

if [ $age -ge 65 ]; then echo "已退休" else echo "未退休" fi

if-elif-else 结构

score=85 if [ $score -ge 90 ]; then echo "优秀" elif [ $score -ge 80 ]; then echo "良好" elif [ $score -ge 60 ]; then echo "及格" else echo "不及格" fi 2. 条件表达式的类型 Shell 支持多种条件表达式,用于不同场景的判断: bash #!/bin/bash

file="example.txt" str="hello" num=10

文件判断

if [ -f "Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_流程控制file 是普通文件" fi

if [ -d "/tmp" ]; then # 目录存在 echo "/tmp 是目录" fi

if [ -r "Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_Shell_02file 可读" fi

字符串判断

if [ -z "$str" ]; then # 字符串为空 echo "字符串为空" else echo "字符串不为空" fi

if [ "$str" = "hello" ]; then # 字符串相等 echo "字符串匹配" fi

数值判断

if [ Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_流程控制_03num 大于 5" fi

if [ Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_流程控制_04num 等于 10" fi 3. 高级条件表达式:[[]] 与 (( )) Bash 提供了更强大的条件表达式语法: bash #!/bin/bash

[[ ]] 支持模式匹配和正则表达式

name="John Doe" if [[ $name == J* ]]; then # 模式匹配(以J开头) echo "姓名以J开头" fi

email="john@example.com" if [[ Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_Shell_05 ]]; then echo "邮箱格式有效" fi

(( )) 支持更直观的算术运算

num=15 if (( num > 10 && num < 20 )); then # 算术表达式 echo "$num 在 10-20 之间" fi

变量自增

(( num++ )) echo "自增后: $num" 二、循环结构:重复执行的艺术 Shell 提供了多种循环结构,适用于不同的迭代场景:

  1. for 循环:遍历列表元素 bash #!/bin/bash

遍历直接列出的元素

for fruit in apple banana cherry date; do echo "水果: $fruit" done

遍历数组

fruits=(apple banana cherry date) for fruit in "${fruits[@]}"; do echo "数组中的水果: $fruit" done

遍历命令输出

echo -e "\n当前目录中的.sh文件:" for file in $(ls *.sh); do echo "- $file" done

C风格的for循环

echo -e "\n计数到5:" for (( i=1; i<=5; i++ )); do echo "计数: $i" done 2. while 循环:条件满足时持续执行 bash #!/bin/bash

基本while循环

count=1 echo "计数到3:" while [ $count -le 3 ]; do echo "计数: $count" (( count++ )) # 计数自增 done

读取文件内容

echo -e "\n读取文件内容:" while IFS= read -r line; do echo "行内容: $line" done < "example.txt" # 从文件读取

无限循环(配合break使用)

echo -e "\n输入q退出:" while true; do read -p "请输入内容: " input if [ "$input" = "q" ]; then break # 退出循环 fi echo "你输入了: $input" done 3. until 循环:条件不满足时持续执行 bash #!/bin/bash

until循环:条件为假时执行

count=5 echo "从5倒数到1:" until [ $count -eq 0 ]; do echo "计数: $count" (( count-- )) done

结合break和continue

echo -e "\n跳过偶数:" num=0 until [ $num -gt 10 ]; do (( num++ )) if (( num % 2 == 0 )); then continue # 跳过偶数 fi echo "奇数: $num" done 4. 循环控制:break 与 continue bash #!/bin/bash

break:跳出整个循环

echo "找到3就停止:" for i in {1..5}; do if [ $i -eq 3 ]; then break fi echo "数字: $i" done

continue:跳过当前迭代

echo -e "\n跳过3:" for i in {1..5}; do if [ $i -eq 3 ]; then continue fi echo "数字: $i" done

嵌套循环中的break

echo -e "\n嵌套循环:" for i in {1..3}; do echo "外部循环: $i" for j in {1..3}; do if [ $j -eq 2 ]; then break # 只跳出内部循环 fi echo " 内部循环: $j" done done 三、循环与条件的组合:实现复杂逻辑 将循环与条件判断结合,可以实现更复杂的业务逻辑:

  1. 批量文件处理 bash #!/bin/bash

批量处理目录中的图片文件

image_dir="./images" output_dir="./resized_images"

创建输出目录

mkdir -p "$output_dir"

遍历所有图片文件

for file in "Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_bash_06file" ] || continue

# 获取文件名(不含路径)
filename=$(basename "$file")# 检查文件是否已经处理过
if [ -f "$output_dir/$filename" ]; thenecho "已处理: $filename,跳过"continue
fi# 处理图片(这里使用convert工具,需要imagemagick)
echo "处理: $filename"
convert "$file" -resize 800x600 "$output_dir/$filename" || {echo "处理 $filename 失败" >&2continue
}

done

echo "批量处理完成" 2. 数据过滤与转换 bash #!/bin/bash

过滤并转换数据文件

input_file="raw_data.txt" output_file="processed_data.txt"

清空输出文件

"$output_file"

echo "开始处理数据..." line_count=0 valid_count=0

读取并处理每一行

while IFS= read -r line; do (( line_count++ ))

# 跳过空行
if [ -z "$line" ]; thencontinue
fi# 跳过注释行
if [[ $line =~ ^# ]]; thencontinue
fi# 提取需要的字段(假设用逗号分隔)
id=$(echo "$line" | cut -d ',' -f 1)
value=$(echo "$line" | cut -d ',' -f 3)# 验证ID格式
if ! [[ $id =~ ^[A-Z]{2}[0-9]{4}$ ]]; thenecho "警告: 行 $line_count 包含无效ID: $id" >&2continue
fi# 验证数值范围
if ! [[ $value =~ ^[0-9]+(\.[0-9]+)?$ ]] || (( $(echo "$value > 100" | bc -l) )); thenecho "警告: 行 $line_count 包含无效值: $value" >&2continue
fi# 处理有效数据(转换为整数)
rounded_value=$(printf "%.0f" "$value")# 写入输出文件
echo "$id,$rounded_value" >> "$output_file"
(( valid_count++ ))

done < "$input_file"

echo "处理完成: 共 $line_count 行,有效 $valid_count 行" echo "结果保存到: $output_file" 四、高级流程控制:函数与循环的协同 将循环逻辑封装到函数中,可以提高代码的可维护性:

  1. 循环函数:处理可复用逻辑 bash #!/bin/bash

检查目录中的文件是否符合命名规范

check_filename_conventions() { local dir="$1" local pattern="$2" local error_count=0

# 验证目录存在
if [ ! -d "$dir" ]; thenecho "错误: 目录 $dir 不存在" >&2return 1
fiecho "检查 $dir 中的文件命名规范..."# 遍历目录中的文件
while IFS= read -r -d '' file; dofilename=$(basename "$file")# 检查文件名是否匹配模式if ! [[ $filename =~ $pattern ]]; thenecho "不符合规范: $filename" >&2(( error_count++ ))fi
done < <(find "$dir" -type f -print0)  # 使用-print0处理含空格的文件名echo "检查完成,发现 $error_count 个不符合规范的文件"
return $error_count

}

主程序

if [ $# -ne 1 ]; then echo "用法: $0 <目录>" exit 1 fi

检查图片文件(假设规范是小写字母、数字和下划线,.jpg或.png结尾)

check_filename_conventions "Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_流程控制_07'

根据检查结果采取行动

if [ $? -eq 0 ]; then echo "所有文件符合命名规范" else echo "请修正不符合规范的文件命名" >&2 # exit 1 # 如需严格检查,可取消注释 fi 2. 递归循环:处理层级结构 bash #!/bin/bash

递归计算目录大小

calculate_dir_size() { local dir="$1" local total_size=0 local item

# 遍历目录中的所有项目
for item in "$dir"/*; do# 跳过不存在的项目(空目录)[ -e "$item" ] || continueif [ -d "$item" ]; then# 递归处理子目录subdir_size=$(calculate_dir_size "$item")total_size=$(( total_size + subdir_size ))elif [ -f "$item" ]; then# 累加文件大小filesize=$(stat -c %s "$item")total_size=$(( total_size + filesize ))fi
done# 显示当前目录大小(转换为KB)
echo "$dir: $(( total_size / 1024 )) KB"
return $total_size

}

主程序

if [ $# -ne 1 ] || [ ! -d "$1" ]; then echo "用法: $0 <目录>" exit 1 fi

echo "计算目录大小(递归):" total=$(calculate_dir_size "$1") echo "总大小: $(( total / 1024 / 1024 )) MB" 五、企业级脚本中的流程控制模式 在大型脚本中,合理组织循环和条件判断能显著提升可读性和可维护性: bash #!/bin/bash

企业级备份脚本:展示复杂流程控制

set -euo pipefail

配置

BACKUP_DIR="/var/backups" SOURCES=("/home" "/etc" "/var/www") RETENTION_DAYS=7 LOG_FILE="/var/log/backup_script.log"

日志函数

log() { local level=$1 local message=Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_bash_08(date +"%Y-%m-%d %H:%M:%S") echo "[Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_流程控制_09level] Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_流程控制_10LOG_FILE" echo "[$level] $message" # 同时输出到控制台 }

检查依赖工具

check_dependencies() { log "INFO" "检查必要工具..." local dependencies=("rsync" "gzip" "find")

for dep in "${dependencies[@]}"; doif ! command -v "$dep" >/dev/null 2>&1; thenlog "ERROR" "缺少必要工具: $dep"exit 1fi
done

}

执行备份

perform_backup() { local source=Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_流程控制_11(basename "Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_bash_12BACKUP_DIR/Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_流程控制_13(date +%Y%m%d).tar.gz"

log "INFO" "开始备份 $source 到 $backup_path"# 创建压缩备份
if tar -czf "$backup_path" -C "$(dirname "$source")" "$(basename "$source")"; thenlog "INFO" "$source 备份成功"return 0
elselog "ERROR" "$source 备份失败"# 清理失败的备份文件if [ -f "$backup_path" ]; thenrm "$backup_path"fireturn 1
fi

}

清理旧备份

cleanup_old_backups() { log "INFO" "清理 $RETENTION_DAYS 天前的旧备份..."

# 查找并删除旧备份
find "$BACKUP_DIR" -type f -name "*.tar.gz" \-mtime +"$RETENTION_DAYS" -print0 | while IFS= read -r -d '' file; dolog "INFO" "删除旧备份: $file"rm "$file"
done

}

主函数

main() { log "INFO" "===== 备份脚本开始执行 ====="

# 检查依赖
check_dependencies# 检查备份目录
if [ ! -d "$BACKUP_DIR" ]; thenlog "INFO" "创建备份目录: $BACKUP_DIR"mkdir -p "$BACKUP_DIR"
fi# 备份每个源目录
local failed=0
for source in "${SOURCES[@]}"; doif [ ! -d "$source" ]; thenlog "WARN" "源目录 $source 不存在,跳过"continuefiif ! perform_backup "$source"; thenfailed=$(( failed + 1 ))fi
done# 清理旧备份
cleanup_old_backups# 总结结果
if [ $failed -eq 0 ]; thenlog "INFO" "所有备份任务成功完成"
elselog "ERROR" "有 $failed 个备份任务失败"exit 1
filog "INFO" "===== 备份脚本执行结束 ====="

}

启动主函数

main "Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_流程控制_14(check_validation "Shell 脚本中的循环与流程控制:从基础到复杂逻辑实现_Shell_15is_valid" = "true" ]) 健壮性考虑 循环处理文件时,始终检查文件是否存在 处理用户输入的循环需设置超时或最大迭代次数 嵌套循环层级不超过 3 层,否则考虑重构 常见陷阱避免 注意文件名中的空格和特殊字符(始终用引号包裹变量) for 循环遍历命令输出时注意字段分隔符(IFS) 管道中的 while 循环在子 shell 中执行,变量修改不会影响外部 掌握这些循环和流程控制技巧,能让你编写的 Shell 脚本从简单的命令堆砌升级为结构化的程序。良好的流程控制设计不仅能实现复杂逻辑,还能保证脚本的可读性和可维护性,这在企业级脚本开发中尤为重要。记住,最适合的控制结构是既能清晰表达逻辑,又能让其他开发者轻松理解的结构。