1. 那些年我们踩过的循环坑

在Linux系统运维和自动化脚本开发中,Bash循环就像老朋友的拥抱——温暖但偶尔会让人窒息。笔者曾用for循环批量处理日志,结果把整个目录清空;也曾在while循环中陷入死循环,导致服务器CPU飙到100%。这些血泪教训告诉我们:Bash循环虽简单,暗坑却不少。

2. 变量作用域:看不见的战场

2.1 未声明的变量刺客

#!/bin/bash
# 错误示例:循环体内修改全局变量
total=0
process_files() {
    for file in *.log
    do
        count=$(wc -l < "$file")
        total=$((total + count))  # 这里埋着定时炸弹
    done
}
process_files
echo "总行数:$total"  # 可能输出0!

解决方案:使用local声明局部变量

process_files() {
    local total=0  # 关键声明
    for file in *.log
    do
        count=$(wc -l < "$file")
        total=$((total + count))
    done
    echo "内部统计:$total"
}

2.2 数组遍历的幽灵索引

# 危险操作:直接修改原数组
files=(*.txt)
for i in "${!files[@]}"
do
    files[i]=$(basename "${files[i]}")  # 修改导致索引错乱
done

正确姿势:创建临时数组

declare -a new_files
for i in "${!files[@]}"
do
    new_files[i]=$(basename "${files[i]}")
done

3. 输入输出的暗流涌动

3.1 管道吞噬变量

# 这个循环永远不会执行
find . -name "*.tmp" | while read file
do
    echo "处理文件: $file"
done

起死回生术:进程替换

while read file
do
    echo "处理文件: $file"
done < <(find . -name "*.tmp")

3.2 标准输出的定时炸弹

for user in $(cat user.list)
do
    passwd --expire "$user"  # 如果user.list有空行?
done

防御之道:IFS与防空行机制

while IFS= read -r user || [[ -n "$user" ]]
do
    [[ -z "$user" ]] && continue
    passwd --expire "$user"
done < user.list

4. 循环控制的花式翻车

4.1 break的越级跳转

for i in {1..10}
do
    {
        sleep 1
        break  # 这个break能跳出循环吗?
    } &
done
wait

真相揭秘:子shell中的break只会影响子进程,需要使用信号控制

4.2 continue的路径迷途

for file in /var/log/*
do
    [[ ! -f "$file" ]] && continue
    # 这里执行耗时操作...
    # 如果按Ctrl+C会怎样?
done

安全方案:添加中断陷阱

trap 'echo "正在安全退出..."; exit' INT
for file in /var/log/*
do
    [[ ! -f "$file" ]] && continue
    # 安全处理逻辑
done

5. 性能优化的双刃剑

5.1 万恶的频繁管道

# 低效写法:每次循环都启动新进程
for i in {1..1000}
do
    grep "ERROR" $i.log | awk '{print $3}'
done

性能飞跃:批量处理模式

awk '/ERROR/{print $3}' *.log > output.txt

5.2 内存杀手:大文件处理

while read line
do
    echo "$line" | sed 's/foo/bar/'
done < huge_file.csv

正确打开方式:流式处理

sed 's/foo/bar/g' huge_file.csv > processed.csv

6. 特殊字符的奇幻漂流

6.1 空格刺客

for file in $(ls *.log)  # 遇到"error log.txt"就翻车
do
    echo "处理文件: $file"
done

终极防御:使用find+xargs

find . -name "*.log" -print0 | xargs -0 -I{} echo "处理文件: {}"

6.2 换行符幽灵

# 处理包含换行符的文件名
find . -type f | while read file
do
    md5sum "$file"  # 遇到含换行符的文件名会出错
done

解决方案:使用-print0

find . -type f -print0 | while IFS= read -r -d '' file
do
    md5sum "$file"
done

7. 调试技巧大全

7.1 实时追踪术

#!/bin/bash -x  # 启用调试模式
for i in {1..3}
do
    echo "第$i次循环"
done

7.2 局部调试法

set -x
for user in alice bob charlie
do
    create_user "$user"
done
set +x

8. 应用场景与技术选型

适用场景

  • 日志文件批量处理
  • 用户账户批量管理
  • 定时任务中的周期操作
  • 自动化测试流程控制

技术优缺点

  • ✔️ 原生支持无需额外依赖
  • ✔️ 与Linux命令完美集成
  • ❌ 缺乏类型检查
  • ❌ 调试难度较高

注意事项

  1. 始终在循环开始前检查边界条件
  2. 处理用户输入时进行过滤和转义
  3. 使用set -euo pipefail增强健壮性
  4. 大数据量处理优先考虑外部命令

9. 总结与展望

通过本文的7大类典型场景分析,我们见识了Bash循环的各种"脾气"。记住三个核心原则:明确变量作用域、警惕特殊字符、善用调试工具。未来在编写循环时,不妨多问自己:这个循环处理空值会怎样?遇到特殊字符会崩溃吗?是否有更高效的外部命令替代?