循环结构是 Shell 脚本中用于重复执行一段代码的控制语句。Bash 支持 for、while 和 until 三种主要循环,结合条件判断和 break、continue 可以实现灵活的流程控制。下面逐一介绍它们的语法和最实用的场景。
14.2.1 for 循环
for 循环用于遍历一个列表,对列表中的每个元素执行相同的操作。基本语法有两种常见形式:
形式一:遍历列表
for 变量 in 列表; do
命令
done
列表可以是直接写出的多个值,也可以是文件名展开、命令替换的结果。例如,批量将当前目录下所有 .txt 文件重命名为 .bak:
for file in *.txt; do
mv "$file" "${file%.txt}.bak"
done
这里 *.txt 会被 Shell 扩展为所有匹配的文件名,${file%.txt} 是去掉后缀的变量操作。
形式二:C 语言风格的 for
for (( 初始表达式; 条件; 步进表达式 )); do
命令
done
这种方式更接近常规编程语言的写法,适合需要明确计数控制的场景。例如,计算 1 到 10 的和:
sum=0
for (( i=1; i<=10; i++ )); do
sum=$(( sum + i ))
done
echo "Sum: $sum"
for 循环很适合处理文件列表、批量任务和已知次数的重复操作。
14.2.2 while 循环
while 循环在条件为真时不断执行循环体,每次执行前先测试条件。语法:
while 条件命令; do
命令
done
最常用的条件是 test 命令(或其别名 [ ])。例如,从 1 打印到 5:
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
count=$(( count + 1 ))
done
while 循环也经常与 read 结合,逐行处理文本文件或命令输出。例如,读取 /etc/passwd 并打印用户名:
while IFS=: read -r user _; do
echo "User: $user"
done < /etc/passwd
这里 IFS=: 指定冒号为字段分隔符,read -r 读取每一行的第一个字段到 user 变量,其余字段丢弃,重定向 < 将文件内容作为循环的输入。这是 Shell 中处理文本数据最经典的用法。
14.2.3 until 循环
until 循环与 while 相反:条件为假时执行循环体,一旦条件为真就停止。语法:
until 条件命令; do
命令
done
它可以用于等待某个状态出现。例如,等待一个服务器端口开始监听(假设端口 8080):
until nc -z localhost 8080; do
sleep 1
done
echo "Server is ready!"
这里 nc -z 检查端口是否开放,若未开放则命令返回非 0(条件为假),循环继续等待。
14.2.4 循环控制:break 与 continue
break:立即跳出整个循环。continue:跳过本次循环剩余的语句,进入下一次迭代。
例如,查找当前目录下第一个大于 1MB 的文件:
for file in *; do
if [ -f "$file" ] && [ $(stat -c%s "$file") -gt 1048576 ]; then
echo "Found large file: $file"
break
fi
done
14.2.5 注意事项与常见陷阱
- 变量引用一定要加双引号:避免文件名中包含空格导致分词错误。例如
mv $file $newfile应写成mv "$file" "$newfile"。 - 管道会创建子 Shell:在
while循环前使用管道(如cat file | while ...)时,循环体可能运行在子 Shell 中,对其内部变量的修改不会影响父 Shell。推荐使用重定向的方式避免这个问题。 - 无穷循环:
while true; do ... done或for ((;;))是有效的无限循环结构,通常配合break和条件判断使用。 - 性能考量:在循环内频繁调用外部命令(如
sed、awk或stat)会显著降低脚本速度。尽可能使用 Shell 内置功能(如参数扩展、$(())算术运算)完成操作。
掌握循环结构后,Shell 脚本才能真正释放威力,将重复、繁琐的手工任务自动化。在实际工作中,结合文件处理、条件判断和函数,你可以构建出强大且稳健的管理工具。