Shell 循环详解

Shell循环简介

循环是Shell脚本编程中实现重复执行的核心结构。通过循环,我们可以高效地处理大量数据、自动化重复任务和执行批量操作。

for循环

基于列表的循环,适合遍历数组、文件列表等。

while循环

基于条件的循环,条件为真时持续执行。

until循环

基于条件的循环,条件为假时持续执行。

break控制

提前退出循环,跳出当前循环结构。

continue控制

跳过当前迭代,继续下一次循环。

嵌套循环

循环内部包含其他循环,处理复杂数据结构。

循环的重要性

循环是Shell脚本实现自动化和批量处理的关键。通过合理的循环结构,脚本可以高效处理文件、数据、用户输入等,大大提高工作效率和脚本的实用性。

for循环

for循环是基于列表的循环,适合遍历数组、文件列表、数字序列等。

for循环执行流程
初始化循环变量
遍历列表中的每个元素
执行循环体代码
还有元素?
循环结束

for循环的基本语法

基本for循环:
for 变量 in 列表
do
  # 循环体代码
done
C风格for循环:
for ((初始化; 条件; 步进))
do
  # 循环体代码
done
for_loop.sh
#!/bin/bash

# for循环示例

echo "=== 基本for循环 ==="
# 遍历单词列表
for fruit in apple banana orange grape; do
echo "水果: $fruit"
done

echo -e "\n=== 遍历数组 ==="
fruits=("apple" "banana" "orange" "grape")
for fruit in "${fruits[@]}"; do
echo "数组元素: $fruit"
done

echo -e "\n=== 遍历数字序列 ==="
# 方法1: 使用序列表达式
for i in {1..5}; do
echo "数字: $i"
done

# 方法2: 使用seq命令
for i in $(seq 1 5); do
echo "seq数字: $i"
done

# 方法3: 指定步长
for i in {1..10..2}; do
echo "步长为2: $i"
done

echo -e "\n=== 遍历文件 ==="
# 遍历当前目录的所有.sh文件
for file in *.sh; do
if [ -f "$file" ]; then
    echo "Shell脚本: $file"
    # 可以在这里处理文件
fi
done

echo -e "\n=== 遍历命令输出 ==="
# 遍历ls命令的输出
for item in $(ls /var/log/ | head -5); do
echo "日志文件/目录: $item"
done

echo -e "\n=== C风格for循环 ==="
# C风格for循环
for ((i=1; i<=5; i++)); do
echo "C风格循环: $i"
done

# 复杂的C风格循环
for ((i=0, j=10; i<=j; i++, j--)); do
echo "i=$i, j=$j"
done

echo -e "\n=== 遍历参数 ==="
# 遍历所有命令行参数
echo "参数个数: $#"
for arg in "$@"; do
echo "参数: $arg"
done

# 使用$*(不推荐,可能有问题)
for arg in $*; do
echo "参数(\$*): $arg"
done

echo -e "\n=== 遍历带空格的字符串 ==="
# 处理包含空格的字符串
names="Alice Bob Charlie"
# 错误的方式(会被空格分割)
echo "错误方式:"
for name in $names; do
echo "名字: $name"
done

# 正确的方式(使用数组)
echo -e "\n正确方式:"
name_array=("Alice" "Bob" "Charlie")
for name in "${name_array[@]}"; do
echo "名字: $name"
done

echo -e "\n=== 遍历目录 ==="
# 遍历目录及其子目录(需要Bash 4.0+)
for file in /var/log/*; do
if [ -d "$file" ]; then
    echo "目录: $file"
elif [ -f "$file" ]; then
    echo "文件: $file"
fi
done

echo -e "\n=== 使用IFS控制分割 ==="
# 修改IFS(内部字段分隔符)
data="apple,banana,orange"
OLD_IFS="$IFS"
IFS=','
for item in $data; do
echo "CSV项: $item"
done
IFS="$OLD_IFS"  # 恢复原来的IFS

echo -e "\n=== 无限for循环 ==="
# 无限循环(需要break退出)
# for ((;;)); do
#     echo "这是一个无限循环"
#     sleep 1
# done
for循环最佳实践:
  • 使用"${array[@]}"而不是"${array[*]}"遍历数组
  • 对包含空格的字符串使用数组而不是普通变量
  • 在遍历文件时先检查文件是否存在
  • 使用C风格循环进行数值迭代
  • 修改IFS时要记得恢复原值

while循环

while循环是基于条件的循环,只要条件为真就持续执行。

while循环执行流程
检查条件
条件为真?
↓是
执行循环体代码
↓否
循环结束

while循环的基本语法

while 条件
do
  # 循环体代码
done
while_loop.sh
#!/bin/bash

# while循环示例

echo "=== 基本while循环 ==="
counter=1
while [ $counter -le 5 ]; do
echo "计数: $counter"
((counter++))
done

echo -e "\n=== 读取文件行 ==="
# 方法1: 使用read和重定向
filename="example.txt"
# 创建示例文件
echo -e "第一行\n第二行\n第三行" > "$filename"

echo "读取文件内容:"
while read line; do
echo "行内容: $line"
done < "$filename"

echo -e "\n=== 读取命令输出 ==="
# 读取命令的每一行输出
echo "进程信息:"
ps aux | head -5 | while read line; do
echo "进程: $line"
done

echo -e "\n=== 使用管道和子shell ==="
# 注意:在管道中的while循环会在子shell中运行
counter=1
echo -e "a\nb\nc" | while read item; do
echo "项目 $counter: $item"
((counter++))
done
echo "计数器值(子shell中修改不会影响父shell): $counter"

echo -e "\n=== 无限while循环 ==="
# 无限循环的几种方式

# 方式1: 使用true命令
# while true; do
#     echo "无限循环..."
#     sleep 1
# done

# 方式2: 使用冒号命令(比true更高效)
# while :; do
#     echo "无限循环..."
#     sleep 1
# done

# 方式3: 使用永远为真的条件
# while [ 1 -eq 1 ]; do
#     echo "无限循环..."
#     sleep 1
# done

echo -e "\n=== 使用break退出循环 ==="
counter=1
while [ $counter -le 10 ]; do
if [ $counter -eq 5 ]; then
    echo "在计数5时退出循环"
    break
fi
echo "计数: $counter"
((counter++))
done

echo -e "\n=== 使用continue跳过迭代 ==="
counter=0
while [ $counter -lt 5 ]; do
((counter++))
if [ $counter -eq 3 ]; then
    echo "跳过计数3"
    continue
fi
echo "计数: $counter"
done

echo -e "\n=== 处理用户输入 ==="
# 简单的菜单系统
while true; do
echo "=== 菜单 ==="
echo "1. 显示当前时间"
echo "2. 显示系统信息"
echo "3. 退出"

read -p "请选择 [1-3]: " choice

case $choice in
    1)
        echo "当前时间: $(date)"
        ;;
    2)
        echo "系统信息: $(uname -a)"
        ;;
    3)
        echo "再见!"
        break
        ;;
    *)
        echo "无效选择,请重新输入"
        ;;
esac

echo
done

echo -e "\n=== 监控文件变化 ==="
# 监控文件变化的简单示例
watch_file="watch.txt"
echo "初始内容" > "$watch_file"
last_mtime=$(stat -c %Y "$watch_file")

echo "监控文件 $watch_file 的变化 (Ctrl+C 退出)"
while true; do
current_mtime=$(stat -c %Y "$watch_file" 2>/dev/null)

if [ "$current_mtime" != "$last_mtime" ]; then
    echo "文件已修改: $(date)"
    echo "新内容: $(cat "$watch_file")"
    last_mtime="$current_mtime"
fi

sleep 2
done

# 清理示例文件
rm -f "$filename" "$watch_file"
while循环注意事项:
  • 确保循环条件最终会变为假,避免无限循环
  • 在管道中的while循环会在子shell中运行,变量修改不会影响父shell
  • 使用break可以提前退出循环
  • 使用continue可以跳过当前迭代
  • 处理用户输入时提供明确的退出选项

until循环

until循环与while循环相反,只要条件为假就持续执行。

until循环执行流程
检查条件
条件为假?
↓是
执行循环体代码
↓否
循环结束
while循环
while [ condition ]; do
  # 条件为真时执行
done

特点:条件为真时执行循环

until循环
until [ condition ]; do
  # 条件为假时执行
done

特点:条件为假时执行循环

until循环的基本语法

until 条件
do
  # 循环体代码
done
until_loop.sh
#!/bin/bash

# until循环示例

echo "=== 基本until循环 ==="
counter=1
until [ $counter -gt 5 ]; do
echo "计数: $counter"
((counter++))
done

echo -e "\n=== 等待条件满足 ==="
# 等待文件创建
target_file="target.txt"
rm -f "$target_file"

echo "等待文件 $target_file 被创建..."
until [ -f "$target_file" ]; do
echo "文件还不存在,等待 1 秒..."
sleep 1
done
echo "文件已创建!"

# 在后台创建文件
(sleep 3; touch "$target_file") &

echo -e "\n=== 等待服务启动 ==="
# 等待端口可用(模拟)
port_available=false
attempt=1
max_attempts=5

until [ "$port_available" = true ] || [ $attempt -gt $max_attempts ]; do
echo "尝试检查端口 (尝试 $attempt/$max_attempts)..."

# 模拟端口检查(随机成功)
if [ $((RANDOM % 3)) -eq 0 ]; then
    port_available=true
    echo "端口现在可用!"
else
    echo "端口还不可用,等待 2 秒..."
    sleep 2
fi

((attempt++))
done

if [ "$port_available" = false ]; then
echo "错误: 在 $max_attempts 次尝试后端口仍然不可用"
fi

echo -e "\n=== 等待用户输入特定值 ==="
# 等待用户输入特定值
valid_input=false
until [ "$valid_input" = true ]; do
read -p "请输入 'yes' 或 'no': " user_input

case $user_input in
    yes|YES|Yes)
        echo "你选择了: yes"
        valid_input=true
        ;;
    no|NO|No)
        echo "你选择了: no"
        valid_input=true
        ;;
    *)
        echo "无效输入,请重新输入"
        ;;
esac
done

echo -e "\n=== 超时控制 ==="
# 带超时的等待
timeout=10
start_time=$(date +%s)
service_ready=false

echo "等待服务准备就绪 (超时: ${timeout}秒)..."
until [ "$service_ready" = true ]; do
current_time=$(date +%s)
elapsed=$((current_time - start_time))

if [ $elapsed -ge $timeout ]; then
    echo "错误: 在 ${timeout} 秒后服务仍未就绪"
    break
fi

# 模拟服务检查
if [ $((RANDOM % 4)) -eq 0 ]; then
    service_ready=true
    echo "服务已就绪! 耗时: ${elapsed}秒"
else
    echo "服务还未就绪,等待 1 秒... (已等待 ${elapsed}秒)"
    sleep 1
fi
done

echo -e "\n=== 处理多个条件 ==="
# 多个条件的until循环
counter=0
max_count=8
target_value=5

until [ $counter -ge $max_count ] || [ $counter -eq $target_value ]; do
echo "计数器: $counter"
((counter++))
done

if [ $counter -eq $target_value ]; then
echo "达到目标值: $target_value"
else
echo "达到最大计数: $max_count"
fi

echo -e "\n=== 与while循环对比 ==="
echo "while循环示例:"
count=1
while [ $count -le 3 ]; do
echo "while: $count"
((count++))
done

echo -e "\nuntil循环示例:"
count=1
until [ $count -gt 3 ]; do
echo "until: $count"
((count++))
done

# 清理
rm -f "$target_file"
until循环使用场景:
  • 等待某个条件变为真(如文件创建、服务启动)
  • 实现超时控制
  • 处理用户输入验证
  • 监控系统状态变化
  • 与while循环逻辑相反,但功能相同

循环控制

使用break和continue控制循环的执行流程。

循环控制语句

break - 退出循环

立即退出当前循环,继续执行循环后面的代码。

continue - 跳过当前迭代

跳过当前循环迭代的剩余代码,直接开始下一次迭代。

loop_control.sh
#!/bin/bash

# 循环控制示例

echo "=== break基本用法 ==="
for i in {1..10}; do
if [ $i -eq 5 ]; then
    echo "在 i=5 时退出循环"
    break
fi
echo "i = $i"
done
echo "循环结束"

echo -e "\n=== continue基本用法 ==="
for i in {1..5}; do
if [ $i -eq 3 ]; then
    echo "跳过 i=3"
    continue
fi
echo "i = $i"
done

echo -e "\n=== break n (退出多层循环) ==="
# 退出指定层数的循环
for i in {1..3}; do
echo "外层循环 i=$i"
for j in {1..3}; do
    echo "  内层循环 j=$j"
    if [ $j -eq 2 ]; then
        echo "  退出两层循环"
        break 2  # 退出两层循环
    fi
done
done

echo -e "\n=== continue n (跳过多层迭代) ==="
# 跳过指定层数的迭代
for i in {1..3}; do
echo "外层循环 i=$i"
for j in {1..3}; do
    if [ $j -eq 2 ]; then
        echo "  跳过外层循环的剩余迭代"
        continue 2  # 跳过外层循环的当前迭代
    fi
    echo "  内层循环 j=$j"
done
done

echo -e "\n=== 在while循环中使用break ==="
count=1
while true; do  # 无限循环
echo "计数: $count"
if [ $count -eq 5 ]; then
    echo "达到目标计数,退出循环"
    break
fi
((count++))
done

echo -e "\n=== 在until循环中使用continue ==="
count=0
until [ $count -gt 5 ]; do
((count++))
if [ $count -eq 3 ]; then
    echo "跳过计数3"
    continue
fi
echo "当前计数: $count"
done

echo -e "\n=== 实际应用:查找文件 ==="
# 在目录树中查找文件
search_file="target.txt"
found=false

# 创建测试目录结构
mkdir -p test_dir/sub1 test_dir/sub2
echo "content" > "test_dir/sub1/file1.txt"
echo "content" > "test_dir/sub2/$search_file"
echo "content" > "test_dir/file2.txt"

echo "查找文件: $search_file"
for dir in test_dir/*; do
if [ -d "$dir" ]; then
    echo "搜索目录: $dir"
    for file in "$dir"/*; do
        if [ -f "$file" ] && [ "$(basename "$file")" = "$search_file" ]; then
            echo "找到文件: $file"
            found=true
            break 2  # 找到文件,退出两层循环
        fi
    done
fi
done

if [ "$found" = false ]; then
echo "未找到文件: $search_file"
fi

echo -e "\n=== 实际应用:处理数据 ==="
# 处理数据,跳过无效条目
data=("正常数据1" "" "正常数据2" "无效数据" "正常数据3")

echo "处理数据列表:"
for item in "${data[@]}"; do
# 跳过空字符串
if [ -z "$item" ]; then
    echo "跳过空数据"
    continue
fi

# 跳过包含"无效"的数据
if [[ "$item" == *"无效"* ]]; then
    echo "跳过无效数据: $item"
    continue
fi

echo "处理数据: $item"
done

echo -e "\n=== 实际应用:菜单系统 ==="
# 带退出选项的菜单系统
while true; do
echo "=== 系统菜单 ==="
echo "1. 显示磁盘使用情况"
echo "2. 显示内存使用情况"
echo "3. 显示进程信息"
echo "4. 退出系统"

read -p "请选择选项 [1-4]: " choice

case $choice in
    1)
        echo "磁盘使用情况:"
        df -h | head -5
        ;;
    2)
        echo "内存使用情况:"
        free -h
        ;;
    3)
        echo "进程信息 (前5个):"
        ps aux | head -6
        ;;
    4)
        echo "退出系统..."
        break
        ;;
    *)
        echo "无效选项,请重新选择"
        continue  # 跳过本次迭代,重新显示菜单
        ;;
esac

echo
read -p "按回车键继续..."
echo
done

# 清理测试文件
rm -rf test_dir
循环控制最佳实践:
  • 使用break提前退出不必要的循环迭代
  • 使用continue跳过不符合条件的迭代
  • break ncontinue n可以控制多层循环
  • 在菜单系统中合理使用breakcontinue
  • 避免过度使用循环控制,保持代码可读性

综合示例

通过实际脚本示例展示Shell循环的综合应用。

文件批量处理工具

使用各种循环结构实现文件的批量处理和管理。

file_processor.sh
#!/bin/bash

# 文件批量处理工具 - 循环综合示例

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 配置
BACKUP_DIR="backups"
LOG_FILE="file_processor.log"

# 初始化
init() {
mkdir -p "$BACKUP_DIR"
echo "$(date): 文件处理器启动" >> "$LOG_FILE"
}

# 记录日志
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"

case $level in
    "ERROR") echo -e "${RED}[ERROR]${NC} $message" ;;
    "WARNING") echo -e "${YELLOW}[WARNING]${NC} $message" ;;
    "INFO") echo -e "${BLUE}[INFO]${NC} $message" ;;
    "SUCCESS") echo -e "${GREEN}[SUCCESS]${NC} $message" ;;
    *) echo "[$level] $message" ;;
esac
}

# 批量重命名文件
batch_rename() {
local pattern="$1"
local prefix="$2"
local counter=1

log "INFO" "开始批量重命名文件,模式: $pattern, 前缀: $prefix"

# 使用for循环遍历匹配的文件
for file in $pattern; do
    if [ -f "$file" ]; then
        local extension="${file##*.}"
        local new_name="${prefix}_${counter}.${extension}"

        if mv "$file" "$new_name" 2>/dev/null; then
            log "SUCCESS" "重命名: $file → $new_name"
            ((counter++))
        else
            log "ERROR" "重命名失败: $file"
        fi
    fi
done

log "INFO" "批量重命名完成,共处理 $((counter-1)) 个文件"
}

# 批量转换文件编码
convert_encoding() {
local pattern="$1"
local from_encoding="$2"
local to_encoding="$3"
local converted=0
local failed=0

log "INFO" "开始转换文件编码: $from_encoding → $to_encoding"

# 使用while循环读取find命令输出
find . -name "$pattern" -type f | while read file; do
    local backup_file="$BACKUP_DIR/$(basename "$file").backup"

    # 创建备份
    if cp "$file" "$backup_file" 2>/dev/null; then
        log "INFO" "创建备份: $backup_file"
    else
        log "ERROR" "备份失败: $file"
        ((failed++))
        continue
    fi

    # 转换编码
    if iconv -f "$from_encoding" -t "$to_encoding" "$file" > "${file}.converted" 2>/dev/null; then
        if mv "${file}.converted" "$file" 2>/dev/null; then
            log "SUCCESS" "转换成功: $file"
            ((converted++))
        else
            log "ERROR" "移动转换文件失败: $file"
            ((failed++))
        fi
    else
        log "ERROR" "转换失败: $file"
        ((failed++))
    fi
done

log "INFO" "编码转换完成: $converted 成功, $failed 失败"
}

# 文件大小统计
file_statistics() {
local pattern="${1:-*}"
local total_size=0
local file_count=0
declare -A size_by_extension

log "INFO" "开始文件统计,模式: $pattern"

# 使用for循环统计文件
for file in $pattern; do
    if [ -f "$file" ]; then
        local size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null)
        local extension="${file##*.}"

        # 如果没有扩展名,使用"无"
        if [ "$file" = "$extension" ]; then
            extension="无"
        fi

        ((total_size += size))
        ((file_count++))
        ((size_by_extension["$extension"] += size))

        log "INFO" "文件: $file, 大小: $size 字节, 扩展名: .$extension"
    fi
done

# 显示统计结果
echo -e "\n${BLUE}=== 文件统计结果 ===${NC}"
echo "总文件数: $file_count"
echo "总大小: $total_size 字节 ($((total_size/1024)) KB)"

if [ $file_count -gt 0 ]; then
    echo -e "\n${GREEN}按扩展名统计:${NC}"
    for ext in "${!size_by_extension[@]}"; do
        local size=${size_by_extension["$ext"]}
        local percentage=$((size * 100 / total_size))
        echo "  .$ext: $size 字节 ($percentage%)"
    done
fi
}

# 监控目录变化
monitor_directory() {
local directory="$1"
local interval="${2:-5}"

if [ ! -d "$directory" ]; then
    log "ERROR" "目录不存在: $directory"
    return 1
fi

log "INFO" "开始监控目录: $directory, 间隔: ${interval}秒"

# 获取初始文件列表
declare -A initial_files
while read -r file; do
    initial_files["$file"]=1
done < <(find "$directory" -type f 2>/dev/null)

# 持续监控
while true; do
    # 获取当前文件列表
    declare -A current_files
    while read -r file; do
        current_files["$file"]=1
    done < <(find "$directory" -type f 2>/dev/null)

    # 检查新增文件
    for file in "${!current_files[@]}"; do
        if [ -z "${initial_files[$file]}" ]; then
            log "INFO" "新增文件: $file"
        fi
    done

    # 检查删除文件
    for file in "${!initial_files[@]}"; do
        if [ -z "${current_files[$file]}" ]; then
            log "WARNING" "删除文件: $file"
        fi
    done

    # 更新文件列表
    initial_files=()
    for file in "${!current_files[@]}"; do
        initial_files["$file"]=1
    done

    # 等待指定间隔
    sleep "$interval"
done
}

# 批量下载管理器
batch_download() {
local url_list_file="$1"
local max_concurrent="${2:-3}"
local downloaded=0
local failed=0

if [ ! -f "$url_list_file" ]; then
    log "ERROR" "URL列表文件不存在: $url_list_file"
    return 1
fi

log "INFO" "开始批量下载,最大并发数: $max_concurrent"

# 读取URL列表
urls=()
while IFS= read -r url || [ -n "$url" ]; do
    urls+=("$url")
done < "$url_list_file"

total_urls=${#urls[@]}
log "INFO" "总共发现 $total_urls 个URL"

# 使用for循环处理URL
for ((i=0; i<total_urls; i++)); do
    local url="${urls[$i]}"
    local filename=$(basename "$url")

    # 如果文件已存在,跳过
    if [ -f "$filename" ]; then
        log "WARNING" "文件已存在,跳过: $filename"
        continue
    fi

    log "INFO" "下载进度: $((i+1))/$total_urls - $filename"

    # 使用wget下载(后台执行)
    if wget -q "$url" -O "$filename" 2>/dev/null; then
        log "SUCCESS" "下载成功: $filename"
        ((downloaded++))
    else
        log "ERROR" "下载失败: $filename"
        ((failed++))
    fi

    # 控制并发数
    local current_jobs=$(jobs -r | wc -l)
    while [ "$current_jobs" -ge "$max_concurrent" ]; do
        sleep 1
        current_jobs=$(jobs -r | wc -l)
    done
done

# 等待所有后台任务完成
wait

log "INFO" "批量下载完成: $downloaded 成功, $failed 失败"
}

# 主菜单
main_menu() {
while true; do
    echo -e "\n${BLUE}=== 文件批量处理工具 ===${NC}"
    echo "1. 批量重命名文件"
    echo "2. 批量转换文件编码"
    echo "3. 文件统计"
    echo "4. 目录监控"
    echo "5. 批量下载"
    echo "6. 退出"

    read -p "请选择操作 [1-6]: " choice

    case $choice in
        1)
            read -p "请输入文件模式 (例如: *.txt): " pattern
            read -p "请输入文件名前缀: " prefix
            batch_rename "$pattern" "$prefix"
            ;;
        2)
            read -p "请输入文件模式 (例如: *.txt): " pattern
            read -p "请输入源编码 (例如: GBK): " from_enc
            read -p "请输入目标编码 (例如: UTF-8): " to_enc
            convert_encoding "$pattern" "$from_enc" "$to_enc"
            ;;
        3)
            read -p "请输入文件模式 (默认: *): " pattern
            file_statistics "${pattern:-*}"
            ;;
        4)
            read -p "请输入要监控的目录: " directory
            read -p "请输入监控间隔(秒,默认: 5): " interval
            monitor_directory "$directory" "${interval:-5}" &
            monitor_pid=$!
            echo "监控已启动 (PID: $monitor_pid),按回车键停止..."
            read
            kill "$monitor_pid" 2>/dev/null
            log "INFO" "目录监控已停止"
            ;;
        5)
            read -p "请输入URL列表文件: " url_file
            read -p "请输入最大并发数 (默认: 3): " max_conn
            batch_download "$url_file" "${max_conn:-3}"
            ;;
        6)
            echo -e "${GREEN}感谢使用文件处理工具,再见!${NC}"
            break
            ;;
        *)
            echo -e "${RED}无效选择,请重新输入${NC}"
            ;;
    esac

    echo
    read -p "按回车键继续..."
done
}

# 脚本入口
main() {
init
echo -e "${GREEN}文件批量处理工具已启动${NC}"
log "INFO" "应用程序启动"
main_menu
log "INFO" "应用程序退出"
}

# 参数处理
case "${1:-}" in
"rename")
    batch_rename "$2" "$3"
    ;;
"convert")
    convert_encoding "$2" "$3" "$4"
    ;;
"stats")
    file_statistics "$2"
    ;;
"monitor")
    monitor_directory "$2" "$3"
    ;;
"download")
    batch_download "$2" "$3"
    ;;
"help"|"-h"|"--help")
    echo "用法: $0 [command]"
    echo "命令:"
    echo "  rename [pattern] [prefix]   批量重命名文件"
    echo "  convert [pattern] [from] [to] 转换文件编码"
    echo "  stats [pattern]             文件统计"
    echo "  monitor [dir] [interval]    监控目录变化"
    echo "  download [file] [max_conn]  批量下载"
    echo "  help                        显示帮助信息"
    echo ""
    echo "示例:"
    echo "  $0 rename '*.txt' doc"
    echo "  $0 convert '*.txt' GBK UTF-8"
    echo "  $0 stats '*.sh'"
    echo "  $0 monitor /tmp 10"
    echo "  $0 download urls.txt 5"
    ;;
*)
    main
    ;;
esac
使用示例:
./file_processor.sh - 启动交互式菜单
./file_processor.sh rename '*.txt' document - 批量重命名txt文件
./file_processor.sh convert '*.txt' GBK UTF-8 - 转换文件编码
./file_processor.sh stats '*.sh' - 统计sh文件
./file_processor.sh help - 显示帮助信息

循环使用最佳实践

推荐做法
  • 使用"${array[@]}"遍历数组
  • 在循环中检查文件是否存在
  • 使用break提前退出不必要的循环
  • 为长时间运行的循环添加进度提示
  • 使用函数封装复杂的循环逻辑
  • 添加适当的错误处理
避免的做法
  • 避免无限循环没有退出条件
  • 不要修改正在遍历的数组
  • 避免在循环中执行耗时操作而没有反馈
  • 不要忽略命令执行失败的情况
  • 避免过度复杂的嵌套循环
  • 不要忘记恢复修改的IFS值
良好实践示例:
# 良好的循环写法
files=()
while IFS= read -r -d '' file; do
  files+=("$file")
done < <(find . -name "*.txt" -type f -print0)

# 处理找到的文件
for file in "${files[@]}"; do
  if [ ! -f "$file" ]; then
    continue
  fi
  echo "处理文件: $file"
  # 实际处理逻辑
done
无限循环注意事项

无限循环必须有明确的退出条件。在生产脚本中,应该为无限循环设置超时机制,并提供用户中断的方式(如Ctrl+C)。监控类脚本应该记录状态并支持优雅退出。